package web

import (
	_ "embed"
	"fmt"
	"html/template"
	"net/http"
	"path/filepath"
	"time"

	"github.com/Masterminds/sprig/v3"
	"github.com/davecgh/go-spew/spew"
	"github.com/fgm/izidic"
	"github.com/gin-contrib/sessions"
	"github.com/gin-contrib/sessions/cookie"
	"github.com/gin-gonic/gin"
	"github.com/utrack/gin-csrf"

	"code.osinet.fr/fgm/sqs_demo/back/services"
	"code.osinet.fr/fgm/sqs_demo/back/services/redriver"
	"code.osinet.fr/fgm/sqs_demo/front"
)

func SetupRoutes(rd redriver.Redriver, renderer *template.Template, storeSecret, csrfSecret []byte) *gin.Engine {
	const assetsPrefix = "/assets/"
	r := gin.Default()
	r.SetHTMLTemplate(renderer)
	_ = r.SetTrustedProxies(nil)
	store := cookie.NewStore(storeSecret)
	r.Use(sessions.Sessions("defaultsession", store))
	mw := csrf.Middleware(csrf.Options{
		Secret: string(csrfSecret),
		ErrorFunc: func(c *gin.Context) {
			c.String(http.StatusBadRequest, "CSRF token does not match")
			c.Abort()
		},
		TokenGetter: nil,
	})

	// Done
	r.StaticFS(assetsPrefix, PrefixFileSystem(assetsPrefix, http.FS(front.Assets)))
	r.GET("/queue", gin.WrapH(http.RedirectHandler("/", http.StatusMovedPermanently)))
	r.GET("/queue/:name/confirm", mw, makeConfirmHandler()) // Needs mw to set token.

	// Back done, front WIP
	r.GET("/queue/:name", makeQueueHandler(rd))

	// JSON done
	r.GET("/", makeHomeHandler(rd))

	// TODO
	r.POST("/queue/:name/delete", mw, makeDeleteHandler(rd))   // Needs mw to check token.
	r.POST("/queue/:name/purge", mw, makePurgeHandler(rd))     // Needs mw to check token.
	r.POST("/queue/:name/redrive", mw, makeRedriveHandler(rd)) // Needs mw to check token.
	return r
}

func HttpService(dic *izidic.Container) (any, error) {
	csrfSecret := dic.MustParam(services.PCSRFSecret).([]byte)
	storeSecret := dic.MustParam(services.PStoreSecret).([]byte)
	rd := dic.MustService(services.SvcRedriver).(redriver.Redriver)
	re := dic.MustService(services.SvcRenderer).(*template.Template)
	return SetupRoutes(rd, re, storeSecret, csrfSecret), nil
}

func RendererService(_ *izidic.Container) (any, error) {
	var err error
	renderer := template.New("redriver").Funcs(template.FuncMap{
		"dump": func(args ...any) template.HTML {
			return "<pre>" + template.HTML(spew.Sdump(args...)) + "</pre>\n"
		},
		"timestamp": func(ts int64) time.Time {
			return time.Unix(ts, 0)
		},
	}).Funcs(sprig.FuncMap())
	for _, tpl := range []struct {
		name  string
		value string
	}{
		{"confirm", front.Confirm},
		{"flashes", front.Flashes},
		{"queue-get", front.QueueGet}, // Includes queue-item.
		{"500", front.Err500},
	} {
		renderer, err = renderer.New(tpl.name).Parse(tpl.value)
		if err != nil {
			return nil, fmt.Errorf("failed parsing %q template: %w", tpl.name, err)
		}
	}

	return renderer, nil
}

// PrefixFileSystem converts a http.FileSystem by serving it requests prefixed
// by the passed prefix, allowing rooted static directories matching the name
// of an embed.FS.
func PrefixFileSystem(prefix string, ifs http.FileSystem) http.FileSystem {
	return &prefixedFS{
		prefix: prefix,
		ifs:    ifs,
	}
}

type prefixedFS struct {
	prefix string
	ifs    http.FileSystem
}

// Open implements http.FileSystem
func (pfs *prefixedFS) Open(name string) (http.File, error) {
	return pfs.ifs.Open(filepath.Join(pfs.prefix, name))
}