package pulls

import (
	"fmt"
	"net/http"
	"time"

	"github.com/bluesky-social/indigo/atproto/syntax"
	"tangled.org/core/api/tangled"
	"tangled.org/core/appview/db"
	"tangled.org/core/appview/models"
	"tangled.org/core/appview/oauth"
	"tangled.org/core/appview/reporesolver"
	"tangled.org/core/orm"
)

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

	user := s.oauth.GetMultiAccountUser(r)
	if user == nil {
		l.Error("nil user")
		s.pages.Notice(w, "pull-action-error", "You must be logged in to merge this pull.")
		return
	}
	l = l.With("user", user.Did)

	f, err := s.repoResolver.Resolve(r)
	if err != nil {
		l.Error("failed to resolve repo", "err", err)
		s.pages.Notice(w, "pull-action-error", "Failed to merge pull request. Try again later.")
		return
	}
	l = l.With("repo", f.RepoDid)

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

	// merge target branch
	{
		client, err := s.oauth.ServiceClient(
			r,
			oauth.WithService(f.Knot),
			oauth.WithLxm(tangled.GitMergeCommitNSID),
			oauth.WithDev(s.config.Core.Dev),
			oauth.WithTimeout(time.Second*20), // merge is quite slow on large repos, like witchsky
		)
		if err != nil {
			l.Error("failed to connect to knot server", "err", err, "knot", f.Knot)
			s.pages.Notice(w, "pull-action-error", "Failed to merge pull request. Try again later.")
			return
		}

		var mergeCommit *tangled.GitMergeCommit_Input_MergeCommit
		// TODO: pass custom merge commit body

		err = tangled.GitMergeCommit(r.Context(), client, &tangled.GitMergeCommit_Input{
			Target: &tangled.GitMergeCommit_Input_Target{
				Repo:   pull.RepoDid.String(),
				Branch: pull.TargetBranch,
			},
			Source: &tangled.GitMergeCommit_Input_Source{
				Repo:   pull.SourceRepo.String(),
				Commit: pull.LatestVersion().Head,
			},
			MergeCommit: mergeCommit,
			Style:  "rebase",
		})
		if err != nil {
			s.logger.Error("failed to merge", "err", err)
			s.pages.Notice(w, "pull-action-error", err.Error())
			return
		}
	}

	if err := s.writePullStatusRecord(r, user.Did, pull.AtUri(), models.StateMerged); err != nil {
		l.Error("failed to write issue state record", "err", err)
		s.pages.Notice(w, "issue-action", "Failed to close issue. Try again later.")
		return
	}

	tx, err := s.db.Begin()
	if err != nil {
		l.Error("failed to start transaction", "err", err)
		s.pages.Notice(w, "pull-action-error", "Failed to merge pull request. Try again later.")
		return
	}
	defer tx.Rollback()

	err = db.MergePulls(
		tx,
		orm.FilterEq("at_uri", pull.AtUri()),
	)
	if err != nil {
		l.Error("failed to update pull request status in database", "err", err)
		s.pages.Notice(w, "pull-action-error", "Failed to merge pull request. Try again later.")
		return
	}

	err = tx.Commit()
	if err != nil {
		// TODO: this is unsound, we should also revert the merge from the knotserver here
		l.Error("failed to commit merge transaction", "err", err)
		s.pages.Notice(w, "pull-action-error", "Failed to merge pull request. Try again later.")
		return
	}

	s.notifier.NewPullState(r.Context(), syntax.DID(user.Did), pull)

	ownerSlashRepo := reporesolver.GetBaseRepoPath(r, f)
	s.pages.HxLocation(w, fmt.Sprintf("/%s/pulls/%d", ownerSlashRepo, pull.PullId))
}
