package pulls

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

	"golang.org/x/sync/errgroup"
	"tangled.org/core/api/tangled"
	"tangled.org/core/appview/db"
	"tangled.org/core/appview/knotcompat"
	"tangled.org/core/appview/models"
	"tangled.org/core/appview/oauth"
	"tangled.org/core/appview/reporesolver"
	"tangled.org/core/tid"

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

func (s *Pulls) handlePull(
	w http.ResponseWriter,
	r *http.Request,
	userDid syntax.DID,
	targetRepo *models.Repo,
	targetBranch string,
	sourceRepo *models.Repo,
	sourceBranch string,
	title,
	body string,
) {
	l := s.logger.With("handler", "handlePull", "user", userDid)
	ctx := r.Context()

	client, err := s.oauth.AuthorizedClient(r)
	if err != nil {
		l.Error("failed to get authorized client", "err", err)
		s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
		return
	}

	// 1. fetch heads of source & target branches
	var base, head string
	{
		xrpcc := &indigoxrpc.Client{Host: s.config.KnotMirror.Url}
		g, gctx := errgroup.WithContext(ctx)
		g.Go(func() error {
			// find merge-base between targetBranch & sourceBranch
			out, err := tangled.GitTempGetMergeBase(gctx, xrpcc, targetBranch, sourceBranch, sourceRepo.RepoDid)
			if err != nil {
				return err
			}
			base = out.Commit
			return nil
		})
		g.Go(func() error {
			out, err := tangled.GitTempGetBranch(gctx, xrpcc, sourceBranch, sourceRepo.RepoDid)
			if err != nil {
				return err
			}
			head = out.Hash
			return nil
		})
		if err := g.Wait(); err != nil {
			l.Error("failed to fetch branch heads", "err", err)
			s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
			return
		}
	}

	created := time.Now()
	pull := &models.Pull{
		ID:       -1, // uninitialized
		OwnerDid: userDid,
		Rkey:     syntax.RecordKey(tid.TID()),
		Cid:      "", // uninitialized
		RepoDid:  syntax.DID(targetRepo.RepoDid),
		PullId:   0, // uninitialized

		Title:        title,
		Body:         body,
		TargetBranch: targetBranch,
		SourceRepo:   syntax.DID(sourceRepo.RepoDid),
		SourceBranch: &sourceBranch,
		Versions: []models.PullVersion{
			{
				ID:      0,
				Base:    base,
				Head:    head,
				Created: created,
			},
		},
		Created: created,
		State:   models.PullOpen,

		Repo: targetRepo,
	}

	// 2. call git.keepCommit
	{
		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 comment to knot", "err", err)
			s.pages.Notice(w, "pull", "Failed to create pull request. 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, "pull", "Failed to create pull request. Try again later.")
			return
		}
	}

	// 3. create PR record
	record := pull.AsRecord()
	out, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
		Collection: tangled.RepoPullNSID,
		Repo:       userDid.String(),
		Rkey:       pull.Rkey.String(),
		Record:     knotcompat.Pull(&record),
	})
	pull.Cid = syntax.CID(out.Cid)

	tx, err := s.db.BeginTx(r.Context(), nil)
	if err != nil {
		l.Error("failed to start tx", "err", err)
		s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
		return
	}
	defer tx.Rollback()

	var references []syntax.ATURI
	if pull.Body != "" {
		_, references = s.mentionsResolver.Resolve(ctx, pull.Body)
	}

	if err := db.PutPull(r.Context(), tx, pull, references); err != nil {
		l.Error("failed to create pull request in database", "err", err)
		s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
		return
	}

	if err = tx.Commit(); err != nil {
		l.Error("failed to commit transaction for pull request", "err", err)
		s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
		return
	}

	s.notifier.NewPull(r.Context(), pull)

	s.applyCreationLabels(r.Context(), client, userDid, pull, r.Form, targetRepo)

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