package pulls

import (
	"context"
	"fmt"
	"net/http"
	"strconv"

	"tangled.org/core/api/tangled"
	"tangled.org/core/appview/db"
	"tangled.org/core/appview/models"
	"tangled.org/core/appview/pages"
	"tangled.org/core/xrpc/xrpcclient"

	"github.com/bluesky-social/indigo/atproto/syntax"
	"github.com/go-chi/chi/v5"
)

// htmx fragment
func (s *Pulls) PullActions(w http.ResponseWriter, r *http.Request) {
	l := s.logger.With("handler", "PullActions")

	switch r.Method {
	case http.MethodGet:
		user := s.oauth.GetMultiAccountUser(r)
		if user != nil {
			l = l.With("user", user.Did)
		}

		f, err := s.repoResolver.Resolve(r)
		if err != nil {
			l.Error("failed to get repo and knot", "err", err)
			return
		}

		pull, ok := r.Context().Value("pull").(*models.Pull)
		if !ok {
			l.Error("failed to get pull")
			s.pages.Notice(w, "pull-error", "Failed to edit patch. Try again later.")
			return
		}
		l = l.With("pull_id", pull.PullId, "pull_owner", pull.OwnerDid)

		versionNumber, err := strconv.Atoi(chi.URLParam(r, "version"))
		if err != nil {
			versionNumber = pull.LatestVersionNumber()
		}
		if versionNumber >= len(pull.Versions) {
			http.Error(w, "bad round id", http.StatusBadRequest)
			l.Error("failed to parse round id", "err", err, "round_number", versionNumber)
			return
		}

		isLastVersion := versionNumber == pull.LatestVersionNumber()

		var workflowsChanged bool
		var changedWorkflows []string
		hasPipeline := false
		if isLastVersion && f.Spindle != "" {
			pipelines := fetchPipelines(r.Context(), l, f, []string{pull.LatestSha()})
			_, hasPipeline = pipelines[pull.LatestSha()]

			if pull.IsForkBased() && !hasPipeline {
				latest := pull.LatestVersion()
				changedWorkflows, err = func(ctx context.Context) ([]string, error) {
					base, err := s.resolveRev(ctx, pull.RepoDid, pull.TargetBranch)
					if err != nil {
						return nil, fmt.Errorf("failed to resolve target branch: %w", err)
					}
					return s.changedWorkflowFiles(ctx, pull.RepoDid, base, pull.SourceRepo, latest.Head)
				}(r.Context())
				if err != nil {
					l.Error("failed to inspect latest round's patch for workflow changes", "err", err)
				}
				workflowsChanged = len(changedWorkflows) > 0
			}
		}

		// only the last round's buttons and banners use merge/resubmit checks
		var mergeCheckParams pages.MergeCheckParams
		var resubmitResult = pages.Unknown
		if isLastVersion {
			mergeCheckParams = s.composeMergeCheck(r.Context(), f, pull.TargetBranch, pull.SourceRepo, pull.LatestVersion().Head)
			if user != nil && syntax.DID(user.Did) == pull.OwnerDid {
				resubmitResult = s.resubmitCheck(r.Context(), pull)
			}
		}

		s.pages.PullActionsFragment(w, pages.PullActionsParams{
			BaseParams:         pages.BaseParamsFromContext(r.Context()),
			RepoInfo:           s.repoResolver.GetRepoInfo(r, user),
			Pull:               pull,
			RoundNumber:        versionNumber,
			MergeCheck:         mergeCheckParams,
			ResubmitCheck:      resubmitResult,
			BranchDeleteStatus: s.branchDeleteStatus(r, f, pull),

			WorkflowsChanged:     workflowsChanged,
			ChangedWorkflowFiles: changedWorkflows,
			HasPipeline:          hasPipeline,
		})
		return
	}
}

func (s *Pulls) branchDeleteStatus(r *http.Request, repo *models.Repo, pull *models.Pull) *pages.BranchDeleteStatus {
	if pull.State != models.PullMerged || pull.SourceBranch == nil {
		return nil
	}

	user := s.oauth.GetMultiAccountUser(r)
	if user == nil {
		return nil
	}

	branch := *pull.SourceBranch

	if syntax.DID(repo.RepoDid) != pull.SourceRepo {
		var err error
		repo, err = db.GetRepoByDid(s.db, pull.SourceRepo.String())
		if err != nil {
			return nil
		}
	}

	// user can only delete branch if they are a collaborator in the repo that the branch belongs to
	if !s.acl.HasRepoPermission(r.Context(), repo, user.Did, "repo:push") {
		return nil
	}

	xrpcc := s.knotMirrorXRPC
	resp, err := tangled.GitTempGetBranch(r.Context(), xrpcc, branch, repo.RepoDid)
	if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
		s.logger.Error("failed to get branch", "xrpcerr", xrpcerr, "err", err)
		return nil
	}

	return &pages.BranchDeleteStatus{
		Repo:   repo,
		Branch: resp.Name,
	}
}

func (s *Pulls) resubmitCheck(ctx context.Context, pull *models.Pull) pages.ResubmitResult {
	if pull.State == models.PullMerged || pull.State == models.PullAbandoned || pull.SourceBranch == nil {
		return pages.Unknown
	}

	sourceSha, err := s.resolveRev(ctx, pull.SourceRepo, *pull.SourceBranch)
	if err != nil {
		s.logger.Error("failed to resolve source branch", "err", err, "pull_id", pull.PullId, "branch", *pull.SourceBranch)
		return pages.Unknown
	}

	if pull.LatestVersion().Head != sourceSha {
		return pages.ShouldResubmit
	}

	return pages.ShouldNotResubmit
}
