package pulls

import (
	"errors"
	"fmt"
	"html/template"
	"io"
	"net/http"
	"strings"

	"github.com/bluesky-social/indigo/atproto/syntax"
	"golang.org/x/sync/errgroup"
	"tangled.org/core/appview/pages"
	gitmirrorv1 "tangled.org/core/gitmirror/proto/gen"
)

// htmx fragment. render interdiff between changes
func (s *Pulls) PullInterdiffFragment(w http.ResponseWriter, r *http.Request) {
	l := s.logger.With("handler", "PullInterdiffFragment")
	ctx := r.Context()
	var (
		repoRaw = r.URL.Query().Get("repo")  // source repo DID
		base1   = r.URL.Query().Get("base1") // base1 commit ID
		head1   = r.URL.Query().Get("head1") // head1 commit ID
		base2   = r.URL.Query().Get("base2") // base2 commit ID
		head2   = r.URL.Query().Get("head2") // head2 commit ID
		unified = r.URL.Query().Get("view") == "unified"
	)
	repo, err := syntax.ParseDID(repoRaw)
	if err != nil {
		http.Error(w, "invalid repo DID", http.StatusBadRequest)
		return
	}
	l.Debug("interdiff", "base1", base1, "head1", head1, "base2", base2, "head2", head2)

	var params pages.PullDiffFragmentParams
	params.DiffUrl = r.URL.Path
	params.Unified = unified
	defer func() {
		s.pages.PullDiffFragment(w, params)
	}()

	stream, err := s.gitmirror.Interdiff(ctx, &gitmirrorv1.InterdiffRequest{
		Repo:     repo.String(),
		FromBase: []byte(base1),
		FromHead: []byte(head1),
		ToBase:   []byte(base2),
		ToHead:   []byte(head2),
	})
	if err != nil {
		l.Error("failed to interdiff", "err", err)
		params.ErrorMsg = "Failed to interdiff. Try again later."
		return
	}
	var files []*fileDiff
	for {
		fd, err := stream.Recv()
		if errors.Is(err, io.EOF) {
			break
		}
		if err != nil {
			l.Error("failed to drain interdiff response", "err", err)
			params.ErrorMsg = "Failed to interdiff. Try again later."
			return
		}
		files = append(files, &fileDiff{diff: fd})
	}

	g, gctx := errgroup.WithContext(ctx)
	for _, f := range files {
		g.Go(func() error {
			lhs, rhs := f.diff.GetLhsSrc(), f.diff.GetRhsSrc()
			// Binary/submodule files have no line content to fetch.
			if isBinaryOrSubmodule(lhs) || isBinaryOrSubmodule(rhs) {
				return nil
			}

			headBlob, err := s.getBlob(gctx, syntax.DID(repo), rhs.GetOid())
			if err != nil {
				return err
			}

			if f.diff.LhsSrc.Content != nil {
				for line := range strings.SplitSeq(strings.TrimSuffix(string(f.diff.LhsSrc.Content), "\n"), "\n") {
					f.baseLines = append(f.baseLines, template.HTML(fmt.Sprintf("<div><span>%s</span></div>", template.HTMLEscapeString(line))))
				}
			}

			for line := range strings.SplitSeq(strings.TrimSuffix(string(headBlob), "\n"), "\n") {
				f.headLines = append(f.headLines, template.HTML(fmt.Sprintf("<div><span>%s</span></div>", template.HTMLEscapeString(line))))
			}
			return nil
		})
	}
	if err := g.Wait(); err != nil {
		l.Error("failed to prepare interdiff UI", "err", err)
		params.ErrorMsg = "Failed to render interdiff. Try again later."
		return
	}
	for _, f := range files {
		df := pages.DiffFile{Path: f.diff.GetRhsSrc().GetPath()}

		if isBinaryOrSubmodule(f.diff.GetLhsSrc()) || isBinaryOrSubmodule(f.diff.GetRhsSrc()) {
			df.Note = "binary or submodule"
			params.Files = append(params.Files, df)
			continue
		}

		for _, h := range buildHunks(f.baseLines, f.headLines, f.diff.Hunks) {
			var dh pages.DiffHunk
			if unified {
				dh.Lines = buildUnifiedLines(h, f.baseLines, f.headLines)
			} else {
				dh.Rows = buildSplitRows(h, f.baseLines, f.headLines)
			}
			df.Hunks = append(df.Hunks, dh)
		}
		params.Files = append(params.Files, df)
	}
}
