// Copyright IBM Corp. 2015, 2025
// SPDX-License-Identifier: BUSL-1.1

package nomad

import (
	"fmt"

	"github.com/hashicorp/nomad/nomad/structs"
)

type jobNamespaceConstraintCheckHook struct {
	srv *Server
}

func (jobNamespaceConstraintCheckHook) Name() string {
	return "namespace-constraint-check"
}

func (c jobNamespaceConstraintCheckHook) Validate(job *structs.Job) (warnings []error, err error) {
	// This was validated before and matches the WriteRequest namespace
	ns, err := c.srv.State().NamespaceByName(nil, job.Namespace)
	if err != nil {
		return nil, err
	}
	if ns == nil {
		return nil, fmt.Errorf("job %q is in nonexistent namespace %q", job.ID, job.Namespace)
	}

	var disallowedDrivers []string
	for _, tg := range job.TaskGroups {
		for _, t := range tg.Tasks {
			if !taskValidateDriver(t, ns) {
				disallowedDrivers = append(disallowedDrivers, t.Driver)
			}
		}
	}
	if len(disallowedDrivers) > 0 {
		if len(disallowedDrivers) == 1 {
			return nil, fmt.Errorf(
				"used task driver %q is not allowed in namespace %q", disallowedDrivers[0], ns.Name,
			)

		} else {
			return nil, fmt.Errorf(
				"used task drivers %q are not allowed in namespace %q", disallowedDrivers, ns.Name,
			)
		}
	}

	var disallowedNetworkModes []string
	for _, tg := range job.TaskGroups {
		for _, network := range tg.Networks {
			if allowed, network_mode := taskValidateNetworkMode(network, ns); !allowed {
				disallowedNetworkModes = append(disallowedNetworkModes, network_mode)
			}
		}
	}
	if len(disallowedNetworkModes) > 0 {
		if len(disallowedNetworkModes) == 1 {
			return nil, fmt.Errorf(
				"used group network mode %q is not allowed in namespace %q", disallowedNetworkModes[0], ns.Name,
			)

		} else {
			return nil, fmt.Errorf(
				"used group network modes %q are not allowed in namespace %q", disallowedNetworkModes, ns.Name,
			)
		}
	}

	return nil, nil
}

func taskValidateNetworkMode(network *structs.NetworkResource, ns *structs.Namespace) (bool, string) {
	network_mode := "host"
	if len(network.Mode) > 0 {
		network_mode = network.Mode
	}
	if ns.Capabilities == nil {
		return true, network_mode
	}
	allow := len(ns.Capabilities.EnabledNetworkModes) == 0
	for _, m := range ns.Capabilities.EnabledNetworkModes {
		if network_mode == m {
			allow = true
			break
		}
	}
	for _, m := range ns.Capabilities.DisabledNetworkModes {
		if network_mode == m {
			allow = false
			break
		}
	}
	return allow, network_mode
}

func taskValidateDriver(task *structs.Task, ns *structs.Namespace) bool {
	if ns.Capabilities == nil {
		return true
	}
	allow := len(ns.Capabilities.EnabledTaskDrivers) == 0
	for _, d := range ns.Capabilities.EnabledTaskDrivers {
		if task.Driver == d {
			allow = true
			break
		}
	}
	for _, d := range ns.Capabilities.DisabledTaskDrivers {
		if task.Driver == d {
			allow = false
			break
		}
	}
	return allow
}
