package xrpc

import (
	"encoding/json"
	"fmt"
	"net/http"
	"path/filepath"

	"github.com/bluesky-social/indigo/atproto/syntax"
	"tangled.org/core/api/tangled"
	"tangled.org/core/knotserver/git"
	"tangled.org/core/rbac"
	"tangled.org/core/types"
	xrpcerr "tangled.org/core/xrpc/errors"
)

func (x *Xrpc) ForkStatus(w http.ResponseWriter, r *http.Request) {
	l := x.Logger.With("handler", "ForkStatus")
	fail := func(e xrpcerr.XrpcError) {
		l.Error("failed", "kind", e.Tag, "error", e.Message)
		writeError(w, e, http.StatusBadRequest)
	}

	actorDid, ok := r.Context().Value(ActorDid).(syntax.DID)
	if !ok {
		fail(xrpcerr.MissingActorDidError)
		return
	}

	var data tangled.RepoForkStatus_Input
	if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
		fail(xrpcerr.GenericError(err))
		return
	}

	did := data.Did
	source := data.Source
	branch := data.Branch
	hiddenRef := data.HiddenRef

	if did == "" || source == "" || branch == "" || hiddenRef == "" {
		fail(xrpcerr.GenericError(fmt.Errorf("did, source, branch, and hiddenRef are required")))
		return
	}

	var name string
	if data.Name != "" {
		name = data.Name
	} else {
		name = filepath.Base(source)
	}

	repoDid, err := x.Db.GetRepoDid(did, name)
	if err != nil {
		fail(xrpcerr.RepoNotFoundError)
		return
	}
	repoPath, _, _, err := x.Db.ResolveRepoDIDOnDisk(x.Config.Repo.ScanPath, repoDid)
	if err != nil {
		fail(xrpcerr.RepoNotFoundError)
		return
	}

	if ok, err := x.Enforcer.IsPushAllowed(actorDid.String(), rbac.ThisServer, repoDid); !ok || err != nil {
		l.Error("insufficient permissions", "did", actorDid.String(), "repo", repoDid)
		writeError(w, xrpcerr.AccessControlError(actorDid.String()), http.StatusUnauthorized)
		return
	}

	gr, err := git.PlainOpen(repoPath)
	if err != nil {
		fail(xrpcerr.GenericError(fmt.Errorf("failed to open repository: %w", err)))
		return
	}

	forkCommit, err := gr.ResolveRevision(branch)
	if err != nil {
		l.Error("error resolving ref revision", "msg", err.Error())
		fail(xrpcerr.GenericError(fmt.Errorf("error resolving revision %s: %w", branch, err)))
		return
	}

	sourceCommit, err := gr.ResolveRevision(hiddenRef)
	if err != nil {
		l.Error("error resolving hidden ref revision", "msg", err.Error())
		fail(xrpcerr.GenericError(fmt.Errorf("error resolving revision %s: %w", hiddenRef, err)))
		return
	}

	status := types.UpToDate
	if forkCommit.Hash.String() != sourceCommit.Hash.String() {
		isAncestor, err := forkCommit.IsAncestor(sourceCommit)
		if err != nil {
			l.Error("error checking ancestor relationship", "error", err.Error())
			fail(xrpcerr.GenericError(fmt.Errorf("error resolving whether %s is ancestor of %s: %w", branch, hiddenRef, err)))
			return
		}

		if isAncestor {
			status = types.FastForwardable
		} else {
			status = types.Conflict
		}
	}

	response := tangled.RepoForkStatus_Output{
		Status: int64(status),
	}

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	json.NewEncoder(w).Encode(response)
}
