package controlplane

import (
	"context"
	"fmt"
	"time"

	cp "github.com/tidbcloud/devops/entity/controlplane"
	"github.com/tidbcloud/devops/pkg/infra/consts"
	"github.com/tidbcloud/devops/pkg/infra/log"
	apprsvcapi "github.com/tidbcloud/devops/pkg/service/approval/api"
	depsvcapi "github.com/tidbcloud/devops/pkg/service/controlplane/api"
)

func (s *svc) CreateDeployment(ctx context.Context, param depsvcapi.CreateDeploymentParam, applicant string) (*uint64, error) {
	mapper, err := GetMapper(param.Scenario)
	if err != nil {
		return nil, err
	}
	if err := mapper.Validate(param.Params); err != nil {
		return nil, fmt.Errorf("parameter validation failed: %w", err)
	}

	dep := &cp.Deployment{
		Scenario:  param.Scenario,
		Phase:     cp.PhaseCreated,
		Applicant: applicant,
		Params:    param.Params,
		CreatedAt: time.Now().Unix(),
	}

	var id uint64
	err = s.dbp.Transaction(ctx, func(ctx context.Context) error {
		saved, err := s.repo.Create(ctx, dep)
		if err != nil {
			return err
		}
		id = saved.ID
		return nil
	})
	if err != nil {
		return nil, err
	}
	return &id, nil
}

func (s *svc) StartPreview(ctx context.Context, id uint64) (*depsvcapi.StartPreviewResult, error) {
	var result *depsvcapi.StartPreviewResult
	err := s.dbp.Transaction(ctx, func(ctx context.Context) error {
		dep, err := s.repo.Get(ctx, id)
		if err != nil {
			return err
		}

		// Allow starting preview from Created or PreviewFailed (re-preview)
		if err := cp.ValidateTransition(dep.Phase, cp.PhasePreviewPending); err != nil {
			return err
		}

		mapper, err := GetMapper(dep.Scenario)
		if err != nil {
			return err
		}

		inputs := mapper.MapInputs(dep.Params, true)
		workflowFile := mapper.WorkflowFileName()

		// Dispatch preview workflow
		runID, runURL, err := s.ghCli.DeployWorkflow(ctx, workflowFile, map[string]interface{}{
			"ref":    "main",
			"inputs": inputs,
		})
		if err != nil {
			return fmt.Errorf("failed to dispatch preview workflow: %w", err)
		}

		// Freeze the snapshot at preview time
		frozenSnapshot := &cp.FrozenSnapshot{
			Params:         dep.Params,
			WorkflowName:   workflowFile,
			WorkflowInputs: inputs,
			FrozenAt:       time.Now().Unix(),
		}
		previewMeta := &cp.WorkflowRunMeta{
			WorkflowName: workflowFile,
			RunID:        runID,
			RunURL:       runURL,
			Status:       "running",
		}

		if err := s.repo.GetAndPatch(ctx, id, func(current *cp.Deployment) error {
			current.Phase = cp.PhasePreviewRunning
			current.FrozenSnapshot = frozenSnapshot
			current.PreviewMeta = previewMeta
			return nil
		}); err != nil {
			return err
		}

		result = &depsvcapi.StartPreviewResult{RunID: runID, RunURL: runURL}
		return nil
	})
	return result, err
}

func (s *svc) OnPreviewComplete(ctx context.Context, id uint64, result depsvcapi.PreviewResult) error {
	return s.dbp.Transaction(ctx, func(ctx context.Context) error {
		dep, err := s.repo.Get(ctx, id)
		if err != nil {
			return err
		}

		if result.Status == "succeeded" {
			// Validate: current → PreviewSucceeded → Approving
			if err := cp.ValidateTransition(dep.Phase, cp.PhasePreviewSucceeded); err != nil {
				return err
			}
			if err := cp.ValidateTransition(cp.PhasePreviewSucceeded, cp.PhaseApproving); err != nil {
				return err
			}

			previewMeta := &cp.WorkflowRunMeta{
				WorkflowName: dep.FrozenSnapshot.WorkflowName,
				RunID:        result.RunID,
				RunURL:       result.RunURL,
				Status:       result.Status,
				Summary:      result.Summary,
			}

			// Build a temporary entity with updated preview evidence for approval creation
			depForApproval := *dep
			depForApproval.PreviewMeta = previewMeta
			approvalID, err := s.createApprovalWithEvidence(ctx, &depForApproval)
			if err != nil {
				return fmt.Errorf("preview succeeded but failed to create approval: %w", err)
			}

			return s.repo.GetAndPatch(ctx, id, func(current *cp.Deployment) error {
				current.Phase = cp.PhaseApproving
				current.PreviewMeta = previewMeta
				current.ApprovalID = approvalID
				return nil
			})
		}

		// Preview failed
		if err := cp.ValidateTransition(dep.Phase, cp.PhasePreviewFailed); err != nil {
			return err
		}
		return s.repo.GetAndPatch(ctx, id, func(current *cp.Deployment) error {
			current.Phase = cp.PhasePreviewFailed
			if current.PreviewMeta == nil {
				current.PreviewMeta = &cp.WorkflowRunMeta{}
			}
			current.PreviewMeta.RunID = result.RunID
			current.PreviewMeta.RunURL = result.RunURL
			current.PreviewMeta.Status = result.Status
			current.PreviewMeta.Summary = result.Summary
			return nil
		})
	})
}

func (s *svc) StartApply(ctx context.Context, id uint64) error {
	return s.dbp.Transaction(ctx, func(ctx context.Context) error {
		dep, err := s.repo.Get(ctx, id)
		if err != nil {
			return err
		}

		if dep.Phase != cp.PhaseApplyPending {
			return fmt.Errorf("can only start apply when phase is %q, current: %q", cp.PhaseApplyPending, dep.Phase)
		}

		if dep.FrozenSnapshot == nil {
			return fmt.Errorf("cannot start apply without a frozen snapshot")
		}

		// Build apply inputs from the frozen snapshot (preview=false)
		inputs := dep.FrozenSnapshot.WorkflowInputs
		applyInputs := make(map[string]string, len(inputs))
		for k, v := range inputs {
			applyInputs[k] = v
		}
		applyInputs["preview"] = "false"

		runID, runURL, err := s.ghCli.DeployWorkflow(ctx, dep.FrozenSnapshot.WorkflowName, map[string]interface{}{
			"ref":    "main",
			"inputs": applyInputs,
		})
		if err != nil {
			return fmt.Errorf("failed to dispatch apply workflow: %w", err)
		}

		return s.repo.GetAndPatch(ctx, id, func(current *cp.Deployment) error {
			current.Phase = cp.PhaseApplyRunning
			current.ApplyMeta = &cp.WorkflowRunMeta{
				WorkflowName: dep.FrozenSnapshot.WorkflowName,
				RunID:        runID,
				RunURL:       runURL,
				Status:       "running",
			}
			return nil
		})
	})
}

func (s *svc) OnApplyComplete(ctx context.Context, id uint64, result depsvcapi.ApplyResult) error {
	return s.dbp.Transaction(ctx, func(ctx context.Context) error {
		dep, err := s.repo.Get(ctx, id)
		if err != nil {
			return err
		}

		var targetPhase cp.Phase
		if result.Status == "succeeded" {
			targetPhase = cp.PhaseApplySucceeded
		} else {
			targetPhase = cp.PhaseApplyFailed
		}

		if err := cp.ValidateTransition(dep.Phase, targetPhase); err != nil {
			return err
		}

		return s.repo.GetAndPatch(ctx, id, func(current *cp.Deployment) error {
			current.Phase = targetPhase
			if current.ApplyMeta == nil {
				current.ApplyMeta = &cp.WorkflowRunMeta{}
			}
			current.ApplyMeta.RunID = result.RunID
			current.ApplyMeta.RunURL = result.RunURL
			current.ApplyMeta.Status = result.Status
			if targetPhase == cp.PhaseApplySucceeded {
				now := time.Now().Unix()
				current.ClosedAt = &now
			}
			return nil
		})
	})
}

func (s *svc) RetryApply(ctx context.Context, id uint64) error {
	return s.dbp.Transaction(ctx, func(ctx context.Context) error {
		dep, err := s.repo.Get(ctx, id)
		if err != nil {
			return err
		}

		if dep.Phase != cp.PhaseApplyFailed {
			return fmt.Errorf("can only retry apply when phase is %q, current: %q", cp.PhaseApplyFailed, dep.Phase)
		}
		if dep.FrozenSnapshot == nil {
			return fmt.Errorf("cannot retry apply without a frozen snapshot")
		}

		// Use the same frozen snapshot — no parameter changes allowed
		inputs := dep.FrozenSnapshot.WorkflowInputs
		applyInputs := make(map[string]string, len(inputs))
		for k, v := range inputs {
			applyInputs[k] = v
		}
		applyInputs["preview"] = "false"

		runID, runURL, err := s.ghCli.DeployWorkflow(ctx, dep.FrozenSnapshot.WorkflowName, map[string]interface{}{
			"ref":    "main",
			"inputs": applyInputs,
		})
		if err != nil {
			return fmt.Errorf("failed to dispatch retry apply workflow: %w", err)
		}

		retryCount := 0
		if dep.ApplyMeta != nil {
			retryCount = dep.ApplyMeta.RetryCount + 1
		}

		return s.repo.GetAndPatch(ctx, id, func(current *cp.Deployment) error {
			current.Phase = cp.PhaseApplyRunning
			current.ApplyMeta = &cp.WorkflowRunMeta{
				WorkflowName: dep.FrozenSnapshot.WorkflowName,
				RunID:        runID,
				RunURL:       runURL,
				Status:       "running",
				RetryCount:   retryCount,
			}
			return nil
		})
	})
}

func (s *svc) Cancel(ctx context.Context, id uint64) error {
	return s.dbp.Transaction(ctx, func(ctx context.Context) error {
		dep, err := s.repo.Get(ctx, id)
		if err != nil {
			return err
		}

		if dep.Phase.IsTerminal() {
			return fmt.Errorf("cannot cancel deployment in terminal phase %q", dep.Phase)
		}

		if err := cp.ValidateTransition(dep.Phase, cp.PhaseCanceled); err != nil {
			return err
		}

		return s.repo.GetAndPatch(ctx, id, func(current *cp.Deployment) error {
			current.Phase = cp.PhaseCanceled
			now := time.Now().Unix()
			current.ClosedAt = &now
			return nil
		})
	})
}

func (s *svc) GetDeployment(ctx context.Context, id uint64) (*cp.Deployment, error) {
	return s.repo.Get(ctx, id)
}

func (s *svc) ListDeployments(ctx context.Context, param depsvcapi.ListDeploymentParam) ([]*cp.Deployment, error) {
	return s.repo.List(ctx, param.Scenario, param.Phase)
}

// createApprovalWithEvidence creates an approval request with preview evidence attached.
func (s *svc) createApprovalWithEvidence(ctx context.Context, dep *cp.Deployment) (*uint64, error) {
	if dep.FrozenSnapshot == nil || dep.PreviewMeta == nil {
		return nil, fmt.Errorf("cannot create approval without preview evidence")
	}

	overview := map[string]string{
		"scenario":        string(dep.Scenario),
		"environment":     dep.Params["environment"],
		"region-name":     dep.Params["region-name"],
		"preview_run_url": dep.PreviewMeta.RunURL,
		"preview_summary": dep.PreviewMeta.Summary,
	}

	parameters := make(map[string]string)
	for k, v := range dep.FrozenSnapshot.Params {
		parameters["param."+k] = v
	}
	parameters["preview_run_id"] = fmt.Sprintf("%d", dep.PreviewMeta.RunID)
	parameters["preview_run_url"] = dep.PreviewMeta.RunURL

	approvalID, err := s.apprSvc.CreateApprovalFromTemplate(ctx, apprsvcapi.CreateApprovalFromTemplateParam{
		CreateApprovalParamBase: apprsvcapi.CreateApprovalParamBase{
			Name:       scenarioToApprovalName(dep.Scenario),
			Applicant:  dep.Applicant,
			DetailLink: fmt.Sprintf("https://%s/deployments/%d", consts.ConsoleAddr(), dep.ID),
			Parameters: parameters,
			Overview:   overview,
			Reason:     fmt.Sprintf("Region deployment preview completed: %s", dep.PreviewMeta.Summary),
		},
		FromTemplate: scenarioToApprovalName(dep.Scenario),
	})
	if err != nil {
		return nil, fmt.Errorf("failed to create approval: %w", err)
	}

	log.FromContext(ctx).Info("created approval for deployment",
		"deploymentID", dep.ID,
		"approvalID", *approvalID,
	)

	return approvalID, nil
}
