package pulls

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

	"github.com/bluesky-social/indigo/atproto/syntax"
	"github.com/bluesky-social/indigo/lex/util"
	indigoxrpc "github.com/bluesky-social/indigo/xrpc"
	"github.com/go-chi/chi/v5"
	"golang.org/x/sync/errgroup"
	"tangled.org/core/api/tangled"
	"tangled.org/core/appview/models"
	"tangled.org/core/appview/pages"
	"tangled.org/core/types"
)

// NOTE: parsing object in middleware is bad pattern
// you will have to check if object exist in context "just in case"
// so it's better to make helper function that can read the url pattern instead.


// A -- B -- C
// (master) (pr/123/0)
//
// A -- B -- C
//  \        (pr/123/0)
//   `-- D <- B' <- C'
//       (master)   (pr/123/1)

// 1. rebase B<-C to D
// 2. compare tree of C and D

// PullInterDiff is router for /pulls/{pull}/{version}..{version}/{change}
//
// Examples:
//   - /pulls/123/0..2/all
//   - /pulls/123/0..2/nrpytyzw
func (s *Pulls) PullInterDiff(w http.ResponseWriter, r *http.Request) {
	l := s.logger.With("handler", "PullRound")
	ctx := r.Context()

	pull, ok := r.Context().Value("pull").(*models.Pull)
	if !ok {
		l.Error("failed to get pull")
		s.pages.Error500(w)
		return
	}

	var (
		version1 = 0
		version2 = 0
		changeId = chi.URLParam(r, "*")
	)
	if changeId == "all" {
		changeId = ""
	}

	// defer render
	var params pages.PullInterdiffParams
	params.Pull = pull
	params.Version1 = version1
	params.Version2 = version2
	params.ChangeId = changeId
	defer s.pages.PullInterdiff(w, params)

	// 1. resolve target branch -> (branch, commit)
	xrpcc := &indigoxrpc.Client{Host: s.config.KnotMirror.Url}
	branch, err := tangled.GitTempGetBranch(ctx, xrpcc, pull.TargetBranch, pull.RepoDid.String())
	if err != nil {
		panic("unimplemented")
	}

	base := branch.Hash
	head1 := "" // pull.Versions[version1].Head
	head2 := "" // pull.Versions[version2].Head

	// 1. log commits from base..head1 and base..head2
	var commits1, commits2 []types.Commit
	g, gctx := errgroup.WithContext(ctx)
	g.Go(func() error {
		commits1, err = getTempListCommits(gctx, xrpcc, pull.SourceRepoDid(), base, head1)
		return err
	})
	g.Go(func() error {
		commits2, err = getTempListCommits(gctx, xrpcc, pull.SourceRepoDid(), base, head2)
		return err
	})
	if err := g.Wait(); err != nil {
		params.ErrorMsg = "something something"
		panic("unimplemented")
	}

	if changeId != "" {
		// interdiff by change-id
		var old, new *types.Commit
		for _, commit := range commits1 {
			if commit.ChangeId == changeId {
				old = &commit
				break
			}
		}
		for _, commit := range commits2 {
			if commit.ChangeId == changeId {
				new = &commit
				break
			}
		}
		_, _ = old, new
		panic("unimplemented")
	} else {
		// interdiff of two commit ranges
		panic("unimplemented")
	}
}

// PullDiff is router for /pulls/{pull}/{version}/{commit}..{commit}
//
// Examples:
//   - /pulls/123/latest
//   - /pulls/123/2/head
//   - /pulls/123/2/base..head
//   - /pulls/123/2/a53ab251e..d8add468c
//   - /pulls/123/2/d8add468c
func (s *Pulls) PullDiff(w http.ResponseWriter, r *http.Request) {
	l := s.logger.With("handler", "PullRound")
	ctx := r.Context()

	pull, ok := r.Context().Value("pull").(*models.Pull)
	if !ok {
		l.Error("failed to get pull")
		s.pages.Error500(w)
		return
	}

	var err error

	var version int
	var versionRaw = chi.URLParam(r, "version")
	if versionRaw == "latest" {
		version = pull.LastRoundNumber()
	} else {
		version, err = strconv.Atoi(versionRaw)
		if err != nil {
			// invalid version number. redirect
			http.Redirect(w, r,
				fmt.Sprintf("/%s/pulls/%d/latest", pull.Repo.RepoIdentifier(), pull.ID),
				http.StatusSeeOther,
			)
			return
		}
	}

	var range_ = chi.URLParam(r, "*")
	base, head, err := parseRevRange(range_)
	if err != nil {
		http.Redirect(w, r,
			fmt.Sprintf("/%s/pulls/%d/%s", pull.Repo.RepoIdentifier(), pull.ID, versionRaw),
			http.StatusSeeOther,
		)
		return
	}

	// defer render
	var params pages.PullDiffParams
	params.Pull = pull
	params.Version = version
	defer s.pages.PullDiff(w, params)

	// 1. resolve target branch -> (branch, commit)
	xrpcc := &indigoxrpc.Client{Host: s.config.KnotMirror.Url}
	branch, err := tangled.GitTempGetBranch(ctx, xrpcc, pull.TargetBranch, pull.RepoDid.String())
	if err != nil {
		l.Warn("Failed to resolve target branch", "branch", pull.TargetBranch, "err", err)
		params.ErrorMsg = fmt.Sprintf("Failed to resolve target branch %q", pull.TargetBranch)
		return
	}

	if base == "base" {
		base = branch.Hash
	}
	if head == "head" {
		head = pull.HEAD()
	}

	sourceRepoDid := pull.SourceRepoDid()

	// 2. list diverged commits using knotmirror (BASE..HEAD) -> ([]commit)
	//    - knotmirror needs on-demand fetch implementation for this
	commits, err := getTempListCommits(ctx, xrpcc, sourceRepoDid, base, head)
	if err != nil {
		panic("unimplemented")
	}

	// 3. list every commits in UI. They will be lazy-loaded
	params.Commits = commits
}

// htmx fragment. render diff between commits
func (s *Pulls) PullDiffFragment(w http.ResponseWriter, r *http.Request) {
	// var (
	// 	base    = r.URL.Query().Get("base") // base commit ID
	// 	head    = r.URL.Query().Get("head") // head commit ID
	// 	unified = r.URL.Query().Get("view") == "unified"
	// )

	// 1. get commit object
	// 2. get diff between parent..commit (knotmirror), parse that diff
	// 3. fetch each file entries (& run syntax highlight) <- skip this part for stage 1. we will do this at stage 2.
	// 4. render diff
}

// htmx fragment. render interdiff between changes
func (s *Pulls) PullInterdiffFragment(w http.ResponseWriter, r *http.Request) {
	// var (
	// 	base1   = r.URL.Query().Get("base1") // base1 commit ID
	// 	base2   = r.URL.Query().Get("base2") // base2 commit ID
	// 	head1   = r.URL.Query().Get("head1") // head1 commit ID
	// 	head2   = r.URL.Query().Get("head2") // head1 commit ID
	// 	unified = r.URL.Query().Get("view") == "unified"
	// )

	// 1. compute interdiff. (knotmirror)
	// 2. return rich diff data. (knotmirror)
	// 3. load old/new blobs & run syntax highlight
	// 4. render diff
}

// parseRevRange parses <head>..<base> string.
// base and head will default to "base" and "head" when omitted.
func parseRevRange(range_ string) (base string, head string, err error) {
	panic("unimplemented")
}

// parseVersionRange parses <version>..<version> string.
// Each versions will default to "base" and "latest" when omitted.
func parseVersionRange(range_ string) (base string, head string, err error) {
	panic("unimplemented")
}

func getTempListCommits(ctx context.Context, xrpcc util.LexClient, repo syntax.DID, base, head string) ([]types.Commit, error) {
	panic("unimplemented")
	// raw, err := tangled.GitTempListCommits(ctx, xrpcc, "", 1000, head, repo.String())
	// if err != nil {
	// 	return nil, err
	// }
	//
	// var xrpcResp types.RepoLogResponse
	// if err := json.Unmarshal(raw, &xrpcResp); err != nil {
	// 	return nil, fmt.Errorf("failed to decode XRPC response: %w", err)
	// }
	//
	// return xrpcResp.Commits, nil
}

// htmx fragment. render interdiff between commits
func (s *Pulls) PullInterDiffFragment(w http.ResponseWriter, r *http.Request) {
	panic("unimplemented")
}

// gitmirror
// - git.ListCommitsSinceMergeBase(repo, base, head)
// - git.Diff(repo, base, head, mode)
// - git.Interdiff(repo, 

// for interdiff, we want: from{start,end}, to{start,end}
// 1. squash from.start ~ from.end into one commit
// 2. rebase that commit to to.start.parent()
// 3. diff from_squashed.tree and to.end.tree

// we want git log BASE..HEAD (only commits in HEAD) diverged=false
//   and git diff BASE...HEAD (changes from HEAD since merge-base) absolute=false

// commands.go:56 picks comparison type:
// - diff BASE..HEAD (COMPARISON_TYPE_ONLY_IN_HEAD) = direct
// - diff BASE...HEAD (COMPARISON_TYPE_INTERSECTION) = merge-base. Server resolves merge-base via g.MergeBase() first (diff.go:43) then diffs.
// we want second one. we should compute merge-base first.
// we can have
