package syw

import (
	"bytes"
	"context"
	"mime"
	"os"
	"path"
	"path/filepath"
	"strings"
	"text/template"

	"tildegit.org/tjp/sliderule"
	"tildegit.org/tjp/sliderule/gemini"
)

const (
	repokey     = "syw_repo"
	reponamekey = "syw_reponame"
)

func GeminiRouter(repodir string, overrides *template.Template) *sliderule.Router {
	tmpl, err := addTemplates(geminiTemplate, overrides)
	if err != nil {
		panic(err)
	}

	repoRouter := &sliderule.Router{}
	repoRouter.Use(assignRepo(repodir))
	repoRouter.Route("/", gmiTemplate(tmpl, "repo_home.gmi"))
	repoRouter.Route("/branches", gmiTemplate(tmpl, "branch_list.gmi"))
	repoRouter.Route("/tags", gmiTemplate(tmpl, "tag_list.gmi"))
	repoRouter.Route("/refs/:ref/", gmiTemplate(tmpl, "ref.gmi"))
	repoRouter.Route("/refs/:ref/tree/*path", geminiTreePath(tmpl))
	repoRouter.Route("/diffstat/:fromref/:toref", runTemplate(tmpl, "diffstat.gmi", "text/plain"))
	repoRouter.Route("/diff/:fromref/:toref", runTemplate(tmpl, "diff.gmi", "text/x-diff"))

	router := &sliderule.Router{}
	router.Route("/", geminiRoot(repodir, tmpl))
	router.Mount("/:"+reponamekey, repoRouter)

	return router
}

func assignRepo(repodir string) sliderule.Middleware {
	return func(h sliderule.Handler) sliderule.Handler {
		return sliderule.HandlerFunc(func(ctx context.Context, request *sliderule.Request) *sliderule.Response {
			repo := Open(filepath.Join(repodir, sliderule.RouteParams(ctx)[reponamekey]))
			return h.Handle(context.WithValue(ctx, repokey, repo), request)
		})
	}
}

func geminiRoot(repodir string, tmpl *template.Template) sliderule.Handler {
	return sliderule.HandlerFunc(func(ctx context.Context, request *sliderule.Request) *sliderule.Response {
		entries, err := os.ReadDir(repodir)
		if err != nil {
			return gemini.Failure(err)
		}

		names := []string{}
		for _, item := range entries {
			if Open(filepath.Join(repodir, item.Name())) != nil {
				names = append(names, item.Name())
			}
		}

		buf := &bytes.Buffer{}
		if err := tmpl.ExecuteTemplate(buf, "repo_root.gmi", names); err != nil {
			return gemini.Failure(err)
		}

		return gemini.Success("text/gemini; charset=utf-8", buf)
	})
}

func geminiTreePath(tmpl *template.Template) sliderule.Handler {
	return sliderule.HandlerFunc(func(ctx context.Context, request *sliderule.Request) *sliderule.Response {
		params := sliderule.RouteParams(ctx)
		if params["path"] == "" || strings.HasSuffix(params["path"], "/") {
			return gmiTemplate(tmpl, "tree.gmi").Handle(ctx, request)
		}

		repo := ctx.Value(repokey).(*Repository)

		body, err := repo.Blob(ctx, params["ref"], params["path"])
		if err != nil {
			return gemini.Failure(err)
		}

		mediaType := ""
		ext := path.Ext(params["path"])
		if ext == ".gmi" {
			mediaType = "text/gemini; charset=utf-8"
		} else {
			mediaType = mime.TypeByExtension(ext)
		}
		if mediaType == "" {
			mediaType = "application/octet-stream"
		}

		return gemini.Success(mediaType, bytes.NewBuffer(body))
	})
}

func gmiTemplate(tmpl *template.Template, name string) sliderule.Handler {
	return runTemplate(tmpl, name, "text/gemini; charset=utf-8")
}

func runTemplate(tmpl *template.Template, name, mimetype string) sliderule.Handler {
	return sliderule.HandlerFunc(func(ctx context.Context, request *sliderule.Request) *sliderule.Response {
		obj := map[string]any{
			"Ctx":    ctx,
			"Repo":   ctx.Value(repokey),
			"Params": sliderule.RouteParams(ctx),
		}
		buf := &bytes.Buffer{}

		if err := tmpl.ExecuteTemplate(buf, name, obj); err != nil {
			return gemini.Failure(err)
		}

		return gemini.Success(mimetype, buf)
	})
}