package xrpc

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

	"github.com/bluesky-social/indigo/atproto/atclient"
	"github.com/bluesky-social/indigo/atproto/syntax"
	"github.com/go-git/go-git/v5/plumbing/filemode"
	"tangled.org/core/api/tangled"
	"tangled.org/core/knotmirror/xrpc/gitea"
)

func (x *Xrpc) GetEntry(w http.ResponseWriter, r *http.Request) {
	var (
		repoQuery = r.URL.Query().Get("repo")
		ref       = cmp.Or(r.URL.Query().Get("ref"), "HEAD")
		path      = r.URL.Query().Get("path")
	)

	repo, err := syntax.ParseDID(repoQuery)
	if err != nil {
		writeJson(w, http.StatusBadRequest, atclient.ErrorBody{Name: "BadRequest", Message: fmt.Sprintf("repo parameter invalid: %s", repoQuery)})
		return
	}

	if path == "" {
		writeJson(w, http.StatusBadRequest, atclient.ErrorBody{Name: "BadRequest", Message: "missing path parameter"})
		return
	}

	l := x.logger.With("method", "git.getEntry", "repo", repo, "ref", ref, "path", path)
	l.Debug("request")

	ctx := r.Context()

	repoPath, err := x.makeRepoPath(ctx, repo)
	if err != nil {
		writeJson(w, http.StatusNotFound, atclient.ErrorBody{Name: "RepoNotFound", Message: fmt.Sprintf("unknown repository: %q", repo)})
		return
	}

	// resolve ref
	commit, err := gitea.GetCommit(ctx, repoPath, ref)
	if err != nil {
		writeJson(w, http.StatusNotFound, atclient.ErrorBody{Name: "RefNotFound", Message: fmt.Sprintf("unknown ref: %q", ref)})
		return
	}
	ref = commit.Hash.String()

	entry, err := gitea.GetEntryFromCommit(ctx, repoPath, commit, path)
	if err != nil {
		writeJson(w, http.StatusNotFound, atclient.ErrorBody{Name: "EntryNotFound", Message: fmt.Sprintf("entry %q not found", path)})
		return
	}
	var size int64
	switch entry.Mode {
	case filemode.Empty, filemode.Dir, filemode.Submodule:
		// no-op
	default:
		size, err = gitea.GetBlobSize(ctx, repoPath, entry.Hash)
		if err != nil {
			l.Error("failed to read blob size", "err", err)
			writeJson(w, http.StatusInternalServerError, atclient.ErrorBody{Name: "InternalServerError", Message: "failed to read blob size"})
			return
		}
	}

	var outLastCommit *tangled.GitTempDefs_Commit
	var outSubmodule *tangled.GitTempDefs_Submodule

	lastCommit, err := gitea.GetCommitByPathWithID(ctx, commit.Hash, repoPath, path)
	if err != nil {
		l.Error("failed to find last commit", "err", err, "repoPath", repoPath)
	} else {
		outLastCommit = &tangled.GitTempDefs_Commit{
			Hash: refString(lastCommit.Hash.String()),
			Tree: refString(lastCommit.TreeHash.String()),
			Author: &tangled.GitTempDefs_Signature{
				Name:  lastCommit.Author.Name,
				Email: lastCommit.Author.Email,
				When:  lastCommit.Author.When.Format(time.RFC3339),
			},
			Committer: &tangled.GitTempDefs_Signature{
				Name:  lastCommit.Committer.Name,
				Email: lastCommit.Committer.Email,
				When:  lastCommit.Committer.When.Format(time.RFC3339),
			},
			Message: lastCommit.Message,
		}
	}

	if entry.Mode == filemode.Submodule {
		modules, err := gitea.GetSubmodules(ctx, repoPath, ref)
		if err != nil && !(errors.Is(err, gitea.ErrMissingGitModules) || errors.Is(err, gitea.ErrInvalidGitModules)) {
			writeJson(w, http.StatusInternalServerError, atclient.ErrorBody{Name: "InternalServerError", Message: "failed to read .gitmodules"})
			return
		}

		if modules != nil {
			for _, submodule := range modules.Submodules {
				if submodule.Path == path {
					outSubmodule = &tangled.GitTempDefs_Submodule{
						Name:   submodule.Name,
						Url:    submodule.URL,
						Branch: refOptionalString(submodule.Branch),
					}
					break
				}
			}
		}
	}

	writeJson(w, http.StatusOK, tangled.GitTempGetEntry_Output{
		Name:       entry.Name,
		Mode:       entry.Mode.String(),
		Oid:        entry.Hash.String(),
		Size:       size,
		LastCommit: outLastCommit,
		Submodule:  outSubmodule,
	})
}

func refString(s string) *string { return &s }
func refOptionalString(s string) *string {
	if s == "" {
		return nil
	}
	return &s
}
