package git

import (
	"bytes"
	"io"
	"net/http"
	"os"
	"path/filepath"

	"git.gabrielgio.me/cerrado/pkg/ext"
	"git.gabrielgio.me/cerrado/pkg/service"
	"git.gabrielgio.me/cerrado/templates"
	"github.com/alecthomas/chroma/v2"
	"github.com/alecthomas/chroma/v2/formatters/html"
	"github.com/alecthomas/chroma/v2/lexers"
	"github.com/alecthomas/chroma/v2/styles"
	"github.com/go-git/go-git/v5/plumbing"
	"github.com/go-git/go-git/v5/plumbing/object"
	"github.com/gomarkdown/markdown"
	markdownhtml "github.com/gomarkdown/markdown/html"
	"github.com/gomarkdown/markdown/parser"
)

type (
	GitHandler struct {
		gitService gitService
		readmePath string
	}

	gitService interface {
		ListRepositories() ([]*service.Repository, error)
		ListCommits(name string, ref string, count int) ([]*object.Commit, error)
		GetHead(name string) (*plumbing.Reference, error)
		GetTree(name, ref, path string) (*object.Tree, error)
		GetFileContent(name, ref, path string) (string, error)
		GetAbout(name string) (string, error)
		ListTags(name string) ([]*plumbing.Reference, error)
		ListBranches(name string) ([]*plumbing.Reference, error)
	}

	configurationRepository interface {
		GetRootReadme() string
	}
)

func NewGitHandler(gitService gitService, confRepo configurationRepository) *GitHandler {
	return &GitHandler{
		gitService: gitService,
		readmePath: confRepo.GetRootReadme(),
	}
}

func (g *GitHandler) List(w http.ResponseWriter, _ *http.Request) error {
	repos, err := g.gitService.ListRepositories()
	if err != nil {
		return err
	}

	f, err := os.Open(g.readmePath)
	if err != nil {
		return err
	}

	bs, err := io.ReadAll(f)
	if err != nil {
		return err
	}

	extensions := parser.CommonExtensions | parser.AutoHeadingIDs | parser.NoEmptyLineBeforeBlock
	p := parser.NewWithExtensions(extensions)
	doc := p.Parse(bs)

	htmlFlag := markdownhtml.CommonFlags | markdownhtml.HrefTargetBlank
	opts := markdownhtml.RendererOptions{Flags: htmlFlag}
	renderer := markdownhtml.NewRenderer(opts)

	bs = markdown.Render(doc, renderer)

	gitList := &templates.GitListPage{
		Respositories: repos,
		About:         bs,
	}
	templates.WritePageTemplate(w, gitList)
	return nil
}

func (g *GitHandler) Summary(w http.ResponseWriter, r *http.Request) error {
	ext.SetHTML(w)
	name := r.PathValue("name")
	ref, err := g.gitService.GetHead(name)
	if err != nil {
		return err
	}

	tags, err := g.gitService.ListTags(name)
	if err != nil {
		return err
	}

	branches, err := g.gitService.ListBranches(name)
	if err != nil {
		return err
	}

	commits, err := g.gitService.ListCommits(name, "", 10)
	if err != nil {
		return err
	}

	gitList := &templates.GitItemPage{
		Name: name,
		Ref:  ref.Name().Short(),
		GitItemBase: &templates.GitItemSummaryPage{
			Tags:     tags,
			Branches: branches,
			Commits:  commits,
		},
	}
	templates.WritePageTemplate(w, gitList)
	return nil
}

func (g *GitHandler) About(w http.ResponseWriter, r *http.Request) error {
	ext.SetHTML(w)
	name := r.PathValue("name")
	ref, err := g.gitService.GetHead(name)
	if err != nil {
		return err
	}

	file, err := g.gitService.GetAbout(name)
	if err != nil {
		return err
	}

	extensions := parser.CommonExtensions | parser.AutoHeadingIDs | parser.NoEmptyLineBeforeBlock
	p := parser.NewWithExtensions(extensions)
	doc := p.Parse([]byte(file))

	htmlFlag := markdownhtml.CommonFlags | markdownhtml.HrefTargetBlank
	opts := markdownhtml.RendererOptions{Flags: htmlFlag}
	renderer := markdownhtml.NewRenderer(opts)

	bs := markdown.Render(doc, renderer)

	gitList := &templates.GitItemPage{
		Name: name,
		Ref:  ref.Name().Short(),
		GitItemBase: &templates.GitItemAboutPage{
			About: bs,
		},
	}
	templates.WritePageTemplate(w, gitList)
	return nil
}

func (g *GitHandler) Refs(w http.ResponseWriter, r *http.Request) error {
	ext.SetHTML(w)
	name := r.PathValue("name")

	tags, err := g.gitService.ListTags(name)
	if err != nil {
		return err
	}

	branches, err := g.gitService.ListBranches(name)
	if err != nil {
		return err
	}

	ref, err := g.gitService.GetHead(name)
	if err != nil {
		return err
	}

	gitList := &templates.GitItemPage{
		Name: name,
		Ref:  ref.Name().Short(),
		GitItemBase: &templates.GitItemRefsPage{
			Tags:     tags,
			Branches: branches,
		},
	}
	templates.WritePageTemplate(w, gitList)
	return nil
}

func (g *GitHandler) Tree(w http.ResponseWriter, r *http.Request) error {
	ext.SetHTML(w)
	name := r.PathValue("name")
	ref := r.PathValue("ref")
	rest := r.PathValue("rest")

	tree, err := g.gitService.GetTree(name, ref, rest)
	if err != nil {
		return err
	}

	gitList := &templates.GitItemPage{
		Name: name,
		Ref:  ref,
		GitItemBase: &templates.GitItemTreePage{
			CurrentPath: rest,
			Tree:        tree,
		},
	}
	templates.WritePageTemplate(w, gitList)
	return nil
}

func (g *GitHandler) Blob(w http.ResponseWriter, r *http.Request) error {
	ext.SetHTML(w)
	name := r.PathValue("name")
	ref := r.PathValue("ref")
	rest := r.PathValue("rest")

	file, err := g.gitService.GetFileContent(name, ref, rest)
	if err != nil {
		return err
	}

	filename := filepath.Base(rest)
	lexer := GetLexers(filename)
	style := styles.Get("xcode")
	formatter := html.New(
		html.WithLineNumbers(true),
	)
	iterator, err := lexer.Tokenise(nil, file)
	if err != nil {
		return err
	}

	var code bytes.Buffer
	err = formatter.Format(&code, style, iterator)
	if err != nil {
		return err
	}

	gitList := &templates.GitItemPage{
		Name: name,
		Ref:  ref,
		GitItemBase: &templates.GitItemBlobPage{
			File:    rest,
			Content: code.Bytes(),
		},
	}
	templates.WritePageTemplate(w, gitList)
	return nil
}

func (g *GitHandler) Log(w http.ResponseWriter, r *http.Request) error {
	ext.SetHTML(w)
	name := r.PathValue("name")
	ref := r.PathValue("ref")

	commits, err := g.gitService.ListCommits(name, ref, 1000)
	if err != nil {
		return err
	}

	gitList := &templates.GitItemPage{
		Name: name,
		Ref:  ref,
		GitItemBase: &templates.GitItemLogPage{
			Commits: commits,
		},
	}
	templates.WritePageTemplate(w, gitList)
	return nil
}

func GetLexers(filename string) chroma.Lexer {
	if filename == "APKBUILD" {
		return lexers.Get("sh")
	}

	lexer := lexers.Get(filename)

	if lexer == nil {
		lexer = lexers.Get("txt")
	}
	return lexer
}