package service

import (
	"compress/gzip"
	"errors"
	"io"
	"log/slog"

	"git.gabrielgio.me/cerrado/pkg/config"
	"git.gabrielgio.me/cerrado/pkg/git"
	"github.com/go-git/go-git/v5/plumbing"
	"github.com/go-git/go-git/v5/plumbing/object"
)

type (
	Repository struct {
		Name           string
		Description    string
		LastCommitDate string
		Ref            string
	}

	GitService struct {
		configRepo configurationRepository
	}

	configurationRepository interface {
		List() []*config.GitRepositoryConfiguration
		GetByName(name string) *config.GitRepositoryConfiguration
	}
)

var ErrRepositoryNotFound = errors.New("Repository not found")

// TODO: make it configurable
const timeFormat = "2006.01.02 15:04:05"

func NewGitService(configRepo configurationRepository) *GitService {
	return &GitService{
		configRepo: configRepo,
	}
}

func (g *GitService) ListRepositories() ([]*Repository, error) {
	rs := g.configRepo.List()

	repos := make([]*Repository, 0, len(rs))
	for _, r := range rs {
		repo, err := git.OpenRepository(r.Path)
		if err != nil {
			return nil, err
		}

		obj, err := repo.LastCommit()
		if err != nil {
			slog.Error("Error fetching last commit", "repository", r.Path, "error", err)
			continue
		}

		head, err := repo.Head()
		if err != nil {
			slog.Error("Error fetching head", "repository", r.Path, "error", err)
			continue
		}

		repos = append(repos, &Repository{
			Name:           r.Name,
			Description:    r.Description,
			LastCommitDate: obj.Author.When.Format(timeFormat),
			Ref:            head.Name().Short(),
		})
	}

	return repos, nil
}

func (g *GitService) ListCommits(name, ref string, count int) ([]*object.Commit, error) {
	r := g.configRepo.GetByName(name)
	if r == nil {
		return nil, ErrRepositoryNotFound
	}

	repo, err := git.OpenRepository(r.Path)
	if err != nil {
		return nil, err
	}

	err = repo.SetRef(ref)
	if err != nil {
		return nil, err
	}
	return repo.Commits(count)
}

func (g *GitService) LastCommit(name, ref string) (*object.Commit, error) {
	r := g.configRepo.GetByName(name)
	if r == nil {
		return nil, ErrRepositoryNotFound
	}

	repo, err := git.OpenRepository(r.Path)
	if err != nil {
		return nil, err
	}

	err = repo.SetRef(ref)
	if err != nil {
		return nil, err
	}

	return repo.LastCommit()
}

func (g *GitService) WriteTarGZip(w io.Writer, name, ref string, prefix string) error {
	r := g.configRepo.GetByName(name)
	if r == nil {
		return ErrRepositoryNotFound
	}

	repo, err := git.OpenRepository(r.Path)
	if err != nil {
		return err
	}

	err = repo.SetRef(ref)
	if err != nil {
		return err
	}

	gw := gzip.NewWriter(w)
	defer gw.Close()

	err = repo.WriteTar(gw, prefix)
	if err != nil {
		return err
	}

	return nil
}

func (g *GitService) Diff(name, ref string) (string, error) {
	r := g.configRepo.GetByName(name)
	if r == nil {
		return "", ErrRepositoryNotFound
	}

	repo, err := git.OpenRepository(r.Path)
	if err != nil {
		return "", err
	}

	err = repo.SetRef(ref)
	if err != nil {
		return "", err
	}

	return repo.Diff()
}

func (g *GitService) GetTree(name, ref, path string) (*object.Tree, error) {
	r := g.configRepo.GetByName(name)
	if r == nil {
		return nil, ErrRepositoryNotFound
	}

	repo, err := git.OpenRepository(r.Path)
	if err != nil {
		return nil, err
	}
	err = repo.SetRef(ref)
	if err != nil {
		return nil, err
	}

	return repo.Tree(path)
}

func (g *GitService) IsBinary(name, ref, path string) (bool, error) {
	r := g.configRepo.GetByName(name)
	if r == nil {
		return false, ErrRepositoryNotFound
	}

	repo, err := git.OpenRepository(r.Path)
	if err != nil {
		return false, err
	}
	err = repo.SetRef(ref)
	if err != nil {
		return false, err
	}

	return repo.IsBinary(path)
}

func (g *GitService) GetFileContent(name, ref, path string) ([]byte, error) {
	r := g.configRepo.GetByName(name)
	if r == nil {
		return nil, ErrRepositoryNotFound
	}

	repo, err := git.OpenRepository(r.Path)
	if err != nil {
		return nil, err
	}
	err = repo.SetRef(ref)
	if err != nil {
		return nil, err
	}

	return repo.FileContent(path)
}

func (g *GitService) GetAbout(name string) ([]byte, error) {
	r := g.configRepo.GetByName(name)
	if r == nil {
		return nil, ErrRepositoryNotFound
	}

	repo, err := git.OpenRepository(r.Path)
	if err != nil {
		return nil, err
	}
	err = repo.SetRef("")
	if err != nil {
		return nil, err
	}

	file, err := repo.FileContent(r.About)
	if err != nil {
		return nil, err
	}

	return file, nil
}

func (g *GitService) ListTags(name string) ([]*git.TagReference, error) {
	r := g.configRepo.GetByName(name)
	if r == nil {
		return nil, ErrRepositoryNotFound
	}

	repo, err := git.OpenRepository(r.Path)
	if err != nil {
		return nil, err
	}
	return repo.Tags()
}

func (g *GitService) ListBranches(name string) ([]*plumbing.Reference, error) {
	r := g.configRepo.GetByName(name)
	if r == nil {
		return nil, ErrRepositoryNotFound
	}

	repo, err := git.OpenRepository(r.Path)
	if err != nil {
		return nil, err
	}
	return repo.Branches()
}

func (g *GitService) GetHead(name string) (*plumbing.Reference, error) {
	r := g.configRepo.GetByName(name)
	if r == nil {
		return nil, ErrRepositoryNotFound
	}

	repo, err := git.OpenRepository(r.Path)
	if err != nil {
		return nil, err
	}

	return repo.Head()
}