package gomod

import (
	"archive/zip"
	"fmt"
	"math/rand"
	"net/url"
	"path"
	"strings"
	"time"

	"github.com/eternal-flame-AD/yoake/internal/echoerror"
	"github.com/labstack/echo/v4"
	"golang.org/x/mod/module"
)

type Info struct {
	Version string    // version string
	Time    time.Time // commit time
}

const backendProxy = "https://proxy.golang.org/"

func random143String() string {
	randItems := []string{
		"143",
		"143.",
		"4.3",
		"143",
		".143",
		"1.43",
		"1.4.3",
		"14.3",
		"omo",
		"om.o",
		"o.m.o",
		"o.mo",
	}

	var ret strings.Builder
	ret.WriteString("v1.4.3-")
	for i := 0; i < 10; i++ {
		rand.Intn(len(randItems))
		ret.WriteString(randItems[rand.Intn(len(randItems))])
	}
	ret.WriteString("+incompatible")
	return ret.String()
}

func resolveModule(c echo.Context, modPathUnesc string, pRequest string) error {
	modPath, err := module.UnescapePath(modPathUnesc)
	if err != nil {
		return echoerror.NewHttp(400, fmt.Errorf("invalid module path: %w", err))
	}
	if !strings.HasPrefix(modPath[strings.IndexRune(modPath, '/')+1:], "test-") {
		return echoerror.NewHttp(400, fmt.Errorf("please prefix your module path with 'test-'"))
	}
	if err != nil {
		return echoerror.NewHttp(400, fmt.Errorf("invalid module path: %v", err))
	}
	if pRequest == "list" {
		return c.String(143, random143String()+"\n")
	}
	ext := path.Ext(pRequest)
	version := strings.TrimSuffix(pRequest, ext)
	if err := module.Check(modPath, version); err != nil {
		return echoerror.NewHttp(400, fmt.Errorf("invalid version: %v", err))
	}
	switch ext {
	case ".info":
		return c.JSON(143, Info{
			Version: version,
			Time:    time.Now().Add(-time.Hour),
		})
	case ".mod":
		return c.String(143, strings.Repeat("\n", 143-1)+"Welcome.to.white.space.\n")
	case ".zip":
		zipFile := zip.NewWriter(c.Response().Writer)
		mainGo, err := zipFile.Create(fmt.Sprintf("%s@%s/main.go", modPath, version))
		if err != nil {
			zipFile.Close()
		}
		_, err = mainGo.Write([]byte(("package main\n\nimport \"fmt\"\n\nfunc main() {\n\n}\n")))
		if err != nil {
			zipFile.Close()
			return echoerror.NewHttp(500, fmt.Errorf("failed to write main.go: %v", err))
		}
		c.Response().Status = 143
		return zipFile.Close()
	}
	return c.Redirect(302, backendProxy+modPathUnesc+"/@v/"+pRequest)
}

func Register(baseURI string, baseG *echo.Group) (gogetMiddleware echo.MiddlewareFunc) {
	baseG.GET("*", func(c echo.Context) error {
		fullURI := c.Request().RequestURI
		if !strings.HasPrefix(fullURI, baseURI) {
			return echo.ErrNotFound
		}
		fullURI = strings.TrimPrefix(fullURI, baseURI)
		fullURIUnEscaped, err := url.PathUnescape(fullURI)
		if err != nil {
			return echoerror.NewHttp(400, fmt.Errorf("invalid URI: %v", err))
		}
		if strings.Contains(fullURIUnEscaped, "/@v/") {
			split := strings.SplitN(fullURIUnEscaped, "/@v/", 2)
			return resolveModule(c, strings.TrimPrefix(split[0], "/"), split[1])
		}
		return echo.ErrNotFound
	})

	return func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c echo.Context) error {
			if c.Request().Method == "GET" && c.QueryParam("go-get") == "1" {
				unescapedPath, err := url.PathUnescape(c.Request().URL.Path)
				if err != nil {
					return echoerror.NewHttp(400, fmt.Errorf("invalid module path: %v", err))
				}
				ctx := goGetHtmlTemplateCtx{
					ModulePath: c.Request().Host + unescapedPath,
					ProxyBase:  c.Scheme() + "://" + c.Request().Host + baseURI,
				}

				return goGetHtmlTemplate.Execute(c.Response().Writer, ctx)
			}
			return next(c)
		}
	}
}