package pulls

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

	"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"

	comatproto "github.com/bluesky-social/indigo/api/atproto"
	"github.com/bluesky-social/indigo/atproto/syntax"
	lexutil "github.com/bluesky-social/indigo/lex/util"
	indigoxrpc "github.com/bluesky-social/indigo/xrpc"
)

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

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

	repo, 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 get PR. Try again later.")
		return
	}
	l = l.With("pull_id", pull.PullId, "pull_owner", pull.OwnerDid)

	noticeId := "resubmit-error"

	if pull.OwnerDid != syntax.DID(user.Did) {
		s.pages.Notice(w, noticeId, "Unauthorized user. Try again later.")
		return
	}

	if pull.SourceBranch == nil {
		// can't resubmit if source is unknown. fail earlier
		s.pages.Notice(w, noticeId, "PR source branch is unknown.")
		return
	}
	sourceBranch := *pull.SourceBranch

	var sourceRepo *models.Repo
	if pull.SourceRepo == syntax.DID(repo.RepoDid) {
		sourceRepo = repo
	} else {
		var err error
		sourceRepo, err = db.GetRepoByDid(s.db, pull.SourceRepo.String())
		if err != nil {
			s.pages.Notice(w, noticeId, fmt.Sprintf("Unknown source repository: %q", pull.SourceRepo))
			return
		}
	}

	ctx := r.Context()

	var base, head string
	{
		xrpcc := &indigoxrpc.Client{Host: s.config.KnotMirror.Url}
		branch, err := tangled.GitTempGetBranch(ctx, xrpcc, sourceBranch, pull.RepoDid.String())
		if err != nil {
			s.pages.Notice(w, noticeId, "Failed to get source branch")
			return
		}
		out, err := tangled.GitTempGetMergeBase(ctx, xrpcc, pull.TargetBranch, branch.Hash, pull.RepoDid.String())
		if err != nil {
			s.pages.Notice(w, noticeId, "Failed to compute merge-base.")
			return
		}

		head = branch.Hash
		base = out.Commit
	}

	newVersion := models.PullVersion{
		ID:      pull.LatestVersionNumber() + 1,
		Base:    base,
		Head:    head,
		Created: time.Now(),
	}
	pull.Versions = append(pull.Versions, newVersion)

	// keep new version head in Knot
	{
		client, err := s.oauth.ServiceClient(
			r,
			oauth.WithService(sourceRepo.Knot),
			oauth.WithLxm(tangled.GitKeepCommitNSID),
			oauth.WithDev(s.config.Core.Dev),
		)
		if err != nil {
			l.Error("failed to create service auth", "err", err)
			s.pages.Notice(w, noticeId, "Failed to create service auth. Try again later.")
			return
		}

		_, err = tangled.GitKeepCommit(ctx, client, &tangled.GitKeepCommit_Input{
			Repo:   sourceRepo.RepoDid,
			Record: pull.AtUri().String(),
			Source: &tangled.GitKeepCommit_Input_Source{
				GitKeepCommit_Commit: &tangled.GitKeepCommit_Commit{
					Repo: sourceRepo.RepoDid,
					Oid:  head,
				},
			},
		})
		if err != nil {
			l.Error("failed to keep commit", "err", err)
			s.pages.Notice(w, noticeId, "Failed to resubmit pull request. Try again later.")
			return
		}
	}

	// update PDS record
	{
		client, err := s.oauth.AuthorizedClient(r)
		if err != nil {
			s.pages.Notice(w, noticeId, "Unauthorized user. Try again later.")
			return
		}

		// NOTE: some old PR records are missing CID
		if err := s.ensurePullCid(ctx, client, pull); err != nil {
			s.pages.Notice(w, noticeId, "Failed to get existing PR record. Is PR deleted from PDS?")
			return
		}

		record := pull.AsRecord()
		exCid := pull.Cid.String()
		out, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
			Collection: tangled.RepoPullNSID,
			Repo:       pull.OwnerDid.String(),
			Rkey:       pull.Rkey.String(),
			SwapRecord: &exCid,
			Record:     &lexutil.LexiconTypeDecoder{Val: &record},
		})
		if err != nil {
			l.Error("failed to create PDS record", "err", err)
			s.pages.Notice(w, noticeId, "Failed to resubmit pull request. Try again later.")
			return
		}
		pull.Cid = syntax.CID(out.Cid)
	}

	if err := db.SubmitPullVersion(ctx, s.db, pull.AtUri(), newVersion); err != nil {
		l.Error("failed to update PR in DB", "err", err)
		s.pages.Notice(w, noticeId, "Failed to resubmit pull request. Try again later.")
		return
	}

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

func (s *Pulls) ensurePullCid(ctx context.Context, client lexutil.LexClient, pull *models.Pull) error {
	out, err := comatproto.RepoGetRecord(ctx, client, "", tangled.RepoPullNSID, pull.OwnerDid.String(), pull.Rkey.String())
	if err != nil {
		return err
	}
	if out.Cid == nil {
		return fmt.Errorf("record CID is empty")
	}
	pull.Cid = syntax.CID(*out.Cid)
	return nil
}
