package validation

import (
	"fmt"
	"slices"

	relent "github.com/tidbcloud/devops/entity/release"
	"github.com/tidbcloud/devops/pkg/infra/utils/container"
)

var (
	Change = changeValidator{}
)

type changeValidator struct{}

func (v changeValidator) ChangeInRepo(change *relent.ChangeInRepo) error {
	if change.Name == "" {
		return ErrRequired("name")
	}

	if change.WindowStartTime().After(change.WindowEndTime()) {
		return fmt.Errorf("window start time must before window end time")
	}

	if len(change.Tier) == 0 {
		return ErrRequired("tier")
	}
	for _, tier := range change.Tier {
		if !slices.Contains(relent.ChangeTierValues, tier) {
			return ErrInvalid("tier", change.Tier)
		}
	}

	if !slices.Contains(relent.RiskLevelValues, change.RiskLevel) {
		return ErrInvalid("risk_level", change.RiskLevel)
	}

	if !slices.Contains(relent.ChangeStatusValues, change.Status) {
		return ErrInvalid("status", change.Status)
	}

	if !slices.Contains(relent.ChangeTypeValues, change.Type) {
		return ErrInvalid("type", change.Type)
	}

	if (change.Type != relent.ChangeTypeHotfix || change.ApplicationName != "ng-image-release") && len(change.ChangeItemNames) == 0 {
		return ErrRequired("change_items")
	}
	for _, name := range change.ChangeItemNames {
		if name == "" {
			return ErrInvalid("change item name", "")
		}
	}

	// change detail
	{
		if change.Detail == nil {
			return ErrRequired("change detail")
		}
		detail := change.Detail

		if detail.StagingTestReport == "" {
			return ErrRequired("staging test report")
		}

	}

	// change deployment
	{
		if len(change.Deployments) == 0 {
			return ErrRequired("deployment")
		}
		orderSet := container.NewSet[relent.ChangeDeployOrder]()
		for _, deployment := range change.Deployments {
			if err := v.Deployment(deployment); err != nil {
				return err
			}
			if orderSet.Has(deployment.Order) {
				return ErrDuplicate("deployment order", deployment.Order)
			}
			orderSet.Insert(deployment.Order)
		}
	}

	// change notification
	{
	}

	if change.ReleaseID == 0 {
		return ErrRequired("release id")
	}

	if change.ApplicantEmail == "" {
		return ErrRequired("applicant email")
	}

	if change.ApplicationName == "" {
		return ErrRequired("application name")
	}

	if change.ApprovalID != nil && *change.ApprovalID == 0 {
		return ErrInvalid("approval id", 0)
	}

	return nil
}

func (v changeValidator) Deployment(deployment *relent.ChangeDeployment) error {
	if deployment == nil {
		return fmt.Errorf("deployment is nil")
	}

	if len(deployment.Regions) == 0 {
		return fmt.Errorf("regions required")
	}

	if !slices.Contains(relent.ChangeDeployMethodValues, deployment.Method) {
		return fmt.Errorf("unknown deployment method %s", deployment.Method)
	}

	if !slices.Contains(relent.ChangeDeployStatusValues, deployment.Status) {
		return fmt.Errorf("unknown deployment status %s", deployment.Status)
	}

	if deployment.Method == relent.ChangeDeployMethodArgoCD {
		if err := v.DeploymentInfoArgoCD(deployment.Info.ArgoCD); err != nil {
			return err
		}
	}

	if deployment.Method == relent.ChangeDeployMethodManual {
		if err := v.DeploymentInfoManual(deployment.Info.Manual); err != nil {
			return err
		}
	}

	if deployment.Method == relent.ChangeDeployMethodEEAPI {
		if err := v.DeploymentInfoEEAPI(deployment.Info.EEAPI); err != nil {
			return err
		}
	}

	return nil
}

func (v changeValidator) DeploymentInfoArgoCD(info *relent.ChangeDeployArgoCD) error {
	if info == nil {
		return fmt.Errorf("deployment info argocd is nil")
	}
	if len(info.DeployPRs) == 0 {
		return fmt.Errorf("deploy prs required")
	}
	for _, pr := range info.DeployPRs {
		if pr.RepoOwner == "" {
			return fmt.Errorf("repo owner is required")
		}
		if pr.RepoName == "" {
			return fmt.Errorf("repo name is required")
		}
		if pr.PRNumber == 0 {
			return fmt.Errorf("pr number is required")
		}
	}

	return nil
}

func (v changeValidator) DeploymentInfoManual(info *relent.ChangeDeployManual) error {
	if info == nil {
		return fmt.Errorf("deployment info manual is nil")
	}

	return nil
}

func (v changeValidator) DeploymentInfoEEAPI(info *relent.ChangeDeployEEAPI) error {
	if info == nil {
		return fmt.Errorf("deployment info eeapi is nil")
	}
	if info.EEAPIBuildNgImage == nil {
		return fmt.Errorf("ee api build ng image is required")
	}
	if info.EEAPIBuildNgImage.Repo == "" {
		return fmt.Errorf("ee api build ng image repo is required")
	}
	if info.EEAPIBuildNgImage.Branch == "" {
		return fmt.Errorf("ee api build ng image branch is required")
	}
	if info.EEAPIBuildNgImage.Commit == "" {
		return fmt.Errorf("ee api build ng image commit is required")
	}
	return nil
}
