package git

import (
	"os"
	"path/filepath"
	"testing"
	"time"

	"github.com/go-git/go-git/v5"
	"github.com/go-git/go-git/v5/plumbing"
	"github.com/go-git/go-git/v5/plumbing/object"

	"olexsmir.xyz/x/is"
)

type testRepo struct {
	tb   testing.TB
	r    *git.Repository
	path string
}

func newTestRepo(tb testing.TB) *testRepo {
	tb.Helper()
	path := tb.TempDir()

	// creates non-bare repo, bare repos require to keep track of HEAD manually
	r, err := git.PlainInit(path, false)
	is.Err(tb, err, nil)

	cfg, err := r.Config()
	is.Err(tb, err, nil)

	cfg.User.Name = "Test User"
	cfg.User.Email = "test@test.local"
	is.Err(tb, r.SetConfig(cfg), nil)

	return &testRepo{tb: tb, path: path, r: r}
}

func (t *testRepo) commitFileAt(name, content, msg string, when time.Time) plumbing.Hash {
	t.tb.Helper()

	filePath := filepath.Join(t.path, name)
	is.Err(t.tb, os.MkdirAll(filepath.Dir(filePath), 0o755), nil)
	is.Err(t.tb, os.WriteFile(filePath, []byte(content), 0o644), nil)

	wt, err := t.r.Worktree()
	is.Err(t.tb, err, nil)

	_, err = wt.Add(name)
	is.Err(t.tb, err, nil)

	hash, err := wt.Commit(msg, &git.CommitOptions{
		Author: &object.Signature{
			Name:  "Test User",
			Email: "test@test.local",
			When:  when,
		},
	})
	is.Err(t.tb, err, nil)
	return hash
}

func (t *testRepo) commitFile(name, content, msg string) plumbing.Hash {
	t.tb.Helper()
	return t.commitFileAt(name, content, msg, time.Now())
}

func (t *testRepo) deleteFile(name, msg string) plumbing.Hash {
	t.tb.Helper()

	wt, err := t.r.Worktree()
	is.Err(t.tb, err, nil)

	_, err = wt.Remove(name)
	is.Err(t.tb, err, nil)

	hash, err := wt.Commit(msg, &git.CommitOptions{
		Author: &object.Signature{
			Name:  "Test User",
			Email: "test@test.local",
			When:  time.Now(),
		},
	})
	is.Err(t.tb, err, nil)
	return hash
}

func (t *testRepo) createBranch(name string, hash plumbing.Hash) {
	t.tb.Helper()
	ref := plumbing.NewHashReference(plumbing.NewBranchReferenceName(name), hash)
	is.Err(t.tb, t.r.Storer.SetReference(ref), nil)
}

func (t *testRepo) checkoutBranch(name string, create bool) {
	t.tb.Helper()

	wt, err := t.r.Worktree()
	is.Err(t.tb, err, nil)

	err = wt.Checkout(&git.CheckoutOptions{
		Branch: plumbing.NewBranchReferenceName(name),
		Create: create,
	})
	is.Err(t.tb, err, nil)
}

func (t *testRepo) createTag(name string, hash plumbing.Hash) {
	t.tb.Helper()
	ref := plumbing.NewHashReference(plumbing.NewTagReferenceName(name), hash)
	is.Err(t.tb, t.r.Storer.SetReference(ref), nil)
}

func (t *testRepo) createAnnotatedTag(name, msg string, hash plumbing.Hash, when time.Time) {
	t.tb.Helper()
	_, err := t.r.CreateTag(name, hash, &git.CreateTagOptions{
		Tagger: &object.Signature{
			Name:  "Test User",
			Email: "test@test.local",
			When:  when,
		},
		Message: msg,
	})
	is.Err(t.tb, err, nil)
}

func (t *testRepo) open(ref ...string) *Repo {
	t.tb.Helper()
	re := ""
	if len(ref) == 1 {
		re = ref[0]
	}
	r, err := Open(t.path, re)
	is.Err(t.tb, err, nil)
	return r
}
