// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package structs

import (
	"github.com/hashicorp/go-set"
)

const (
	// JobServiceRegistrationsRPCMethod is the RPC method for listing all
	// service registrations assigned to a specific namespaced job.
	//
	// Args: JobServiceRegistrationsRequest
	// Reply: JobServiceRegistrationsResponse
	JobServiceRegistrationsRPCMethod = "Job.GetServiceRegistrations"
)

// JobServiceRegistrationsRequest is the request object used to list all
// service registrations belonging to the specified Job.ID.
type JobServiceRegistrationsRequest struct {
	JobID string
	QueryOptions
}

// JobServiceRegistrationsResponse is the response object when performing a
// listing of services belonging to a namespaced job.
type JobServiceRegistrationsResponse struct {
	Services []*ServiceRegistration
	QueryMeta
}

// NativeServiceDiscoveryUsage tracks which groups make use of the nomad service
// discovery provider, and also which of those groups make use of checks. This
// information will be used to configure implicit constraints on the job.
type NativeServiceDiscoveryUsage struct {
	Basic  *set.Set[string] // implies v1.3.0 + ${attr.nomad.service_discovery}
	Checks *set.Set[string] // implies v1.4.0
}

// Empty returns true if no groups are using native service discovery.
func (u *NativeServiceDiscoveryUsage) Empty() bool {
	return u.Basic.Size() == 0 && u.Checks.Size() == 0
}

// RequiredNativeServiceDiscovery identifies which task groups, if any, within
// the job are utilising Nomad native service discovery.
func (j *Job) RequiredNativeServiceDiscovery() *NativeServiceDiscoveryUsage {
	basic := set.New[string](10)
	checks := set.New[string](10)

	for _, tg := range j.TaskGroups {
		// It is possible for services using the Nomad provider to be
		// configured at the group level, so check here first.
		requiresNativeServiceDiscovery(tg.Name, tg.Services, basic, checks)

		// Iterate the tasks within the task group to check the services
		// configured at this more traditional level.
		for _, task := range tg.Tasks {
			requiresNativeServiceDiscovery(tg.Name, task.Services, basic, checks)
		}
	}
	return &NativeServiceDiscoveryUsage{
		Basic:  basic,
		Checks: checks,
	}
}

// requiresNativeServiceDiscovery identifies whether any of the services passed
// to the function are utilising Nomad native service discovery.
func requiresNativeServiceDiscovery(group string, services []*Service, basic, checks *set.Set[string]) {
	for _, tgService := range services {
		if tgService.Provider == ServiceProviderNomad {
			basic.Insert(group)
			if len(tgService.Checks) > 0 {
				checks.Insert(group)
			}
		}
	}
}

// RequiredConsulServiceDiscovery identifies which task groups, if any, within
// the job are utilising Consul service discovery.
func (j *Job) RequiredConsulServiceDiscovery() map[string]bool {
	groups := make(map[string]bool)

	for _, tg := range j.TaskGroups {

		// It is possible for services using the Consul provider to be
		// configured at the task group level, so check here first. This is
		// a requirement for Consul Connect services.
		if requiresConsulServiceDiscovery(tg.Services) {
			groups[tg.Name] = true
			continue
		}

		// Iterate the tasks within the task group to check the services
		// configured at this more traditional level.
		for _, task := range tg.Tasks {
			if requiresConsulServiceDiscovery(task.Services) {
				groups[tg.Name] = true
				continue
			}
		}
	}

	return groups
}

// requiresConsulServiceDiscovery identifies whether any of the services passed
// to the function are utilising Consul service discovery.
func requiresConsulServiceDiscovery(services []*Service) bool {
	for _, tgService := range services {
		if tgService.Provider == ServiceProviderConsul || tgService.Provider == "" {
			return true
		}
	}
	return false
}
