package controlplane

import (
	"context"
	"fmt"

	apprent "github.com/tidbcloud/devops/entity/approval"
	cp "github.com/tidbcloud/devops/entity/controlplane"
	"github.com/tidbcloud/devops/entity/event"
	depsvcapi "github.com/tidbcloud/devops/pkg/service/controlplane/api"
)

func (s *svc) HandleApprovalEvent(_ string, ev *event.ApprovalEvent) error {
	if ev.EventType != event.ApprovalEventTypeFinish {
		return nil
	}

	approvalID := ev.ID
	ctx := context.TODO()

	dep, err := s.repo.GetByApprovalID(ctx, approvalID)
	if err != nil {
		s.logger.Error(err, "failed to find deployment for approval", "approvalID", approvalID)
		return nil
	}

	if dep.Phase != cp.PhaseApproving {
		return nil
	}
	if ev.Details.Finish == nil {
		return nil
	}

	switch ev.Details.Finish.Result {
	case apprent.ApprovalStatusApproved:
		// Transition to apply_pending; apply is triggered manually by the user via the /apply endpoint.
		if err := s.repo.GetAndPatch(ctx, dep.ID, func(current *cp.Deployment) error {
			current.Phase = cp.PhaseApplyPending
			return nil
		}); err != nil {
			s.logger.Error(err, "failed to transition deployment to apply_pending",
				"deploymentID", dep.ID,
				"approvalID", approvalID,
			)
		}
	case apprent.ApprovalStatusRejected, apprent.ApprovalStatusCancelled:
		if err := s.Cancel(ctx, dep.ID); err != nil {
			s.logger.Error(err, "failed to cancel deployment after approval rejection",
				"deploymentID", dep.ID,
				"approvalID", approvalID,
			)
		}
	}

	return nil
}

// PollWorkflowRuns polls GitHub Actions for in-progress workflow runs and updates deployment status.
func (s *svc) PollWorkflowRuns(ctx context.Context) error {
	// Find all deployments that are waiting for workflow results
	previewPending := cp.PhasePreviewPending
	previewRunning := cp.PhasePreviewRunning
	applyRunning := cp.PhaseApplyRunning

	for _, phase := range []*cp.Phase{&previewPending, &previewRunning, &applyRunning} {
		deps, err := s.repo.List(ctx, nil, phase)
		if err != nil {
			s.logger.Error(err, "failed to list deployments for polling", "phase", *phase)
			continue
		}

		for _, dep := range deps {
			s.pollDeploymentRun(ctx, dep)
		}
	}

	return nil
}

func (s *svc) pollDeploymentRun(ctx context.Context, dep *cp.Deployment) {
	isPreview := dep.Phase == cp.PhasePreviewPending || dep.Phase == cp.PhasePreviewRunning
	meta := dep.ApplyMeta
	if isPreview {
		meta = dep.PreviewMeta
	}

	if meta == nil || meta.RunID == 0 {
		return
	}

	run, err := s.ghCli.GetWorkflowRun(ctx, meta.RunID)
	if err != nil {
		s.logger.Error(err, "failed to get workflow run",
			"deploymentID", dep.ID,
			"runID", meta.RunID,
		)
		return
	}

	switch run.Status {
	case "completed":
		var status string
		if run.Conclusion == "success" {
			status = "succeeded"
		} else {
			status = "failed"
		}

		if isPreview {
			if err := s.OnPreviewComplete(ctx, dep.ID, depsvcapi.PreviewResult{
				RunID:   meta.RunID,
				RunURL:  run.HTMLURL,
				Status:  status,
				Summary: fmt.Sprintf("Workflow run %s with conclusion: %s", run.Status, run.Conclusion),
			}); err != nil {
				s.logger.Error(err, "failed to complete preview", "deploymentID", dep.ID)
			}
		} else {
			if err := s.OnApplyComplete(ctx, dep.ID, depsvcapi.ApplyResult{
				RunID:  meta.RunID,
				RunURL: run.HTMLURL,
				Status: status,
			}); err != nil {
				s.logger.Error(err, "failed to complete apply", "deploymentID", dep.ID)
			}
		}

	case "in_progress", "queued":
		// Update phase to running if still pending
		if dep.Phase == cp.PhasePreviewPending || dep.Phase == cp.PhaseApplyPending {
			targetPhase := cp.PhasePreviewRunning
			if !isPreview {
				targetPhase = cp.PhaseApplyRunning
			}
			_ = s.repo.GetAndPatch(ctx, dep.ID, func(current *cp.Deployment) error {
				if current.Phase == dep.Phase {
					current.Phase = targetPhase
					if isPreview && current.PreviewMeta != nil {
						current.PreviewMeta.RunURL = run.HTMLURL
					} else if !isPreview && current.ApplyMeta != nil {
						current.ApplyMeta.RunURL = run.HTMLURL
					}
				}
				return nil
			})
		}
	}
}
