package validation

import (
	"slices"

	apprent "github.com/tidbcloud/devops/entity/approval"
	"github.com/tidbcloud/devops/pkg/infra/utils/container"
)

var (
	Approval = approvalValidator{}
)

type approvalValidator struct{}

func (v approvalValidator) Approval(appr *apprent.Approval) error {
	if appr == nil {
		return ErrRequired("approval")
	}

	if appr.Name == "" {
		return ErrRequired("name")
	}

	if appr.Applicant == "" {
		return ErrRequired("applicant")
	}

	if !slices.Contains(apprent.ApprovalStatusValues, appr.Status) {
		return ErrInvalid("status", appr.Status)
	}

	if len(appr.Levels) > 10 {
		return ErrInvalid("nodes count must be less than 10", len(appr.Levels))
	}

	for index, apprLevel := range appr.Levels {
		err := v.ApprovalLevel(apprLevel)
		if err != nil {
			return err
		}

		if apprLevel.Level != index {
			return ErrField("levels.level", "must be equal to its index in approval.Levels")
		}
	}

	return nil
}

func (v approvalValidator) ApprovalLevel(levelNodes *apprent.ApprovalLevel) error {
	if levelNodes == nil {
		return ErrRequired("approvalLevel")
	}

	nodeNameSet := container.NewSet[string]()

	for _, node := range levelNodes.Nodes {
		err := v.ApprovalNode(node)
		if err != nil {
			return err
		}

		if nodeNameSet.Has(node.Name) {
			return ErrDuplicate("node.name", node.Name)
		}

		nodeNameSet.Insert(node.Name)
	}

	return nil
}

func (v approvalValidator) ApprovalNode(node *apprent.ApprovalNode) error {
	if node == nil {
		return ErrRequired("node")
	}

	if !slices.Contains(apprent.ApproveMethodValues, node.Method) {
		return ErrInvalid("method", node.Method)
	}

	if len(node.Tasks) == 0 {
		return ErrRequired("tasks")
	}
	if len(node.Tasks) > 20 {
		return ErrInvalid("tasks count must be less than 20", len(node.Tasks))
	}

	userSet := container.NewSet[string]()
	for _, task := range node.Tasks {
		err := v.ApprovalTask(task)
		if err != nil {
			return err
		}
		if userSet.Has(task.User) {
			return ErrDuplicate("node.task.user", task.User)
		}
		userSet.Insert(task.User)
	}

	return nil
}

func (v approvalValidator) ApprovalTask(task *apprent.ApprovalTask) error {
	if task == nil {
		return ErrRequired("task")
	}

	if task.User == "" {
		return ErrRequired("user")
	}

	if !slices.Contains(apprent.ApprovalTaskStatusValues, task.Status) {
		return ErrInvalid("status", task.Status)
	}

	return nil
}
