// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT

package repository

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"code.forgejo.org/f3/gof3/v3/f3"
	helpers_repository "code.forgejo.org/f3/gof3/v3/forges/helpers/repository"
	"code.forgejo.org/f3/gof3/v3/tree/generic"
	"code.forgejo.org/f3/gof3/v3/util"

	"github.com/stretchr/testify/assert"
)

type TestHelper struct {
	t     TestingT
	clone string
	bare  string
	node  generic.NodeInterface
}

func NewTestHelper(t TestingT, bare string, node generic.NodeInterface) *TestHelper {
	o := &TestHelper{
		t:    t,
		node: node,
	}

	o.bare = bare
	if o.bare == "" {
		if node == nil {
			panic("")
		}
		bare, err := os.MkdirTemp("", "repositoryHelperBare")
		assert.NoError(t, err)
		o.bare = bare
		o.InitBare()
		node.ToFormat().(*f3.Repository).FetchFunc(context.Background(), bare, "")
	} else {
		o.InitBare()
	}

	clone, err := os.MkdirTemp("", "repositoryHelperClone")
	assert.NoError(t, err)
	o.clone = clone
	util.Command(context.Background(), nil, "git", "clone", o.bare, o.clone)
	o.setRepositoryConfig()

	return o
}

func (o *TestHelper) GetClone() string {
	return o.clone
}

func (o *TestHelper) PullClone() {
	util.Command(context.Background(), nil, "git", "-C", o.clone, "pull")
}

func (o *TestHelper) GetBare() string {
	return o.bare
}

func (o *TestHelper) GetNode() generic.NodeInterface {
	return o.node
}

func (o *TestHelper) InitBare(args ...string) string {
	if !util.FileExists(o.bare) {
		assert.NoError(o.t, os.Mkdir(o.bare, 0o700))
	}
	init := []string{"-C", o.bare, "init", "--bare"}
	init = append(init, args...)
	return util.Command(context.Background(), nil, "git", init...)
}

func (o *TestHelper) RevList() string {
	return util.Command(context.Background(), nil, "git", "-C", o.clone, "rev-list", "--all")
}

func (o *TestHelper) AssertReadmeContains(content string) {
	o.PullClone()
	readme, err := os.ReadFile(filepath.Join(o.GetClone(), "README.md"))
	assert.NoError(o.t, err)
	assert.Contains(o.t, string(readme), content)
}

func (o *TestHelper) AssertRepositoryNotFileExists(file string) {
	assert.NotContains(o.t, o.repositoryLsFile(file), file)
}

func (o *TestHelper) AssertRepositoryFileExists(file string) {
	assert.Contains(o.t, o.repositoryLsFile(file), file)
}

func (o *TestHelper) AssertRepositoryTagExists(tag string) {
	assert.EqualValues(o.t, tag+"\n", util.Command(context.Background(), nil, "git", "-C", o.clone, "tag", "-l", tag))
}

func (o *TestHelper) AssertRepositoryBranchExists(branch string) {
	assert.EqualValues(o.t, "refs/heads/"+branch+"\n", util.Command(context.Background(), nil, "git", "-C", o.bare, "branch", "--format", "%(refname)", "-l", branch))
}

func (o *TestHelper) repositoryCountObject() int64 {
	out := util.Command(context.Background(), nil, "git", "-C", o.clone, "count-objects")
	count := strings.Split(out, " ")[0]
	return util.ParseInt(count)
}

func (o *TestHelper) repositoryLsFile(file string) string {
	if o.repositoryCountObject() > 0 {
		return util.Command(context.Background(), nil, "git", "-C", o.clone, "ls-tree", "HEAD", file)
	}
	return ""
}

func (o *TestHelper) PushMirror() {
	util.Command(context.Background(), nil, "git", "-C", o.clone, "push", "--all", "origin")
	util.Command(context.Background(), nil, "git", "-C", o.clone, "push", "--tags", "origin")
	if o.node != nil {
		f := o.node.ToFormat().(*f3.Repository)
		f.FetchFunc = func(ctx context.Context, destination, internalRef string) {
			helpers_repository.GitMirror(ctx, nil, o.bare, destination, internalRef)
		}
		if f.GetID() == "" {
			panic(fmt.Errorf("id %s", o.node.GetID()))
		}
		o.node.FromFormat(f)
		o.node.Upsert(context.Background())
	}
}

func (o *TestHelper) CreateRepositoryContent(content string) *TestHelper {
	o.commitREADME(content)
	return o
}

func (o *TestHelper) CreateRepositoryTag(tag, commit string) *TestHelper {
	o.createRepositoryTag(tag, commit)
	return o
}

func (o *TestHelper) createRepositoryTag(tag, commit string) {
	util.Command(context.Background(), nil, "git", "-C", o.clone, "tag", tag, commit)
}

func (o *TestHelper) setRepositoryConfig() {
	util.Command(context.Background(), nil, "git", "-C", o.clone, "config", "user.email", "author@example.com")
	util.Command(context.Background(), nil, "git", "-C", o.clone, "config", "user.name", "Author")
}

func (o *TestHelper) commitREADME(content string) {
	readme := fmt.Sprintf("# Testing Repository\n\nOriginally created in: %s\n%s", o.clone, content)
	assert.NoError(o.t, os.WriteFile(filepath.Join(o.clone, "README.md"), []byte(readme), 0o644))
	util.Command(context.Background(), nil, "git", "-C", o.clone, "add", "README.md")
	util.Command(context.Background(), nil, "git", "-C", o.clone, "commit", "-m", "Add README", "README.md")
}

func (o *TestHelper) GetRepositorySha(branch string) string {
	sha := util.Command(context.Background(), nil, "git", "-C", o.clone, "rev-parse", "origin/"+branch)
	return strings.TrimSuffix(sha, "\n")
}

func (o *TestHelper) BranchRepositoryFeature(branch, content string) *TestHelper {
	o.InternalBranchRepositoryFeature(branch, content)
	return o
}

func (o *TestHelper) InternalBranchRepositoryFeature(branch, content string) {
	util.Command(context.Background(), nil, "git", "-C", o.clone, "checkout", "-b", branch, "master")
	assert.NoError(o.t, os.WriteFile(filepath.Join(o.clone, "README.md"), []byte(content), 0o644))
	util.Command(context.Background(), nil, "git", "-C", o.clone, "add", "README.md")
	util.Command(context.Background(), nil, "git", "-C", o.clone, "commit", "-m", "feature README", "README.md")
	util.Command(context.Background(), nil, "git", "-C", o.clone, "push", "origin", branch)
	util.Command(context.Background(), nil, "git", "-C", o.clone, "checkout", "master")
}
