package main

import (
	"context"
	"embed"
	"fmt"
	"html/template"
	"io/fs"
	"net/http"
	"os/signal"
	"syscall"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/robfig/cron/v3"
	"github.com/sethvargo/go-password/password"
	log "github.com/sirupsen/logrus"
)

type AdminConfig struct {
	Users   []*User        `json:"users"`
	Secrets *SecretsConfig `json:"secrets"`
	Addr    string         `json:"addr"`
}

type SecretsConfig struct {
	PasswordPepper    string `json:"password_pepper"`
	ContextKey        string `json:"context_key"`
	ContextExpiration int    `json:"context_expiration"`
	ScryptN           int    `json:"scrypt_n"`
	ScryptR           int    `json:"scrypt_r"`
	ScryptP           int    `json:"scrypt_p"`
}

//go:embed assets
var assets embed.FS

func NewAdmin() *AdminConfig {
	log.WithFields(log.Fields{}).Debugf("starting")
	defer log.WithFields(log.Fields{}).Debugf("done")

	a := &AdminConfig{
		Addr:    "0.0.0.0:8080",
		Secrets: NewSecrets(),
		Users:   make([]*User, 0),
	}

	return a
}

func NewSecrets() *SecretsConfig {
	log.WithFields(log.Fields{}).Debugf("starting")
	defer log.WithFields(log.Fields{}).Debugf("done")

	pepper, _ := password.Generate(20, 5, 0, false, false)
	ctx, _ := password.Generate(20, 5, 0, false, false)

	return &SecretsConfig{
		PasswordPepper:    pepper,
		ContextKey:        ctx,
		ContextExpiration: 3600,
		ScryptN:           32768,
		ScryptR:           8,
		ScryptP:           1,
	}
}

func (a *AdminConfig) NewAdminUser() {
	log.WithFields(log.Fields{}).Debugf("starting")
	defer log.WithFields(log.Fields{}).Debugf("done")

	p, _ := password.Generate(20, 5, 0, false, false)
	u, _ := NewUser("admin", p)

	a.Users = append(a.Users, u)

	log.WithFields(log.Fields{}).Warnf("Admin user password :  %s", p)
}

func (a *AdminConfig) Run() {
	log.WithFields(log.Fields{}).Debugf("starting")
	defer log.WithFields(log.Fields{}).Debugf("done")

	// Create context that listens for the interrupt signal from the OS.
	ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
	defer stop()

	if !*debug {
		gin.SetMode(gin.ReleaseMode)
	}

	r := gin.Default()

	if t, err := template.ParseFS(assets, "assets/templates/*.html"); err != nil {
		log.WithFields(log.Fields{"call": "template.ParseFS", "error": err}).Errorf("")
		return
	} else {
		r.SetHTMLTemplate(t)
	}

	r.GET("/", HttpAnyIndex)
	r.POST("/", HttpAnyIndex)

	r.GET("/ping", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"message": "pong",
		})
	})

	r.GET("/run", func(c *gin.Context) {
		cfg.Run()
		c.JSON(http.StatusOK, gin.H{
			"message": "done",
		})
	})

	r.GET("/save", func(c *gin.Context) {
		if err := cfg.Save(); err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{
				"message": "error",
			})
		} else {
			c.JSON(http.StatusOK, gin.H{
				"message": "done",
			})
		}
	})

	r.GET("/test", func(c *gin.Context) {
		res := make(map[string]string)
		for k, v := range cfg.box {
			if err := v.Open(); err != nil {
				res[k] = fmt.Sprintf("%s", err)
			} else {
				res[k] = "ok"
			}
			v.Close()
		}
		c.JSON(http.StatusOK, gin.H{
			"message": res,
		})
	})

	fsys, _ := fs.Sub(assets, "assets/static")
	r.StaticFS("/assets", http.FS(fsys))

	protected := r.Group("p", HttpAuth())
	protected.GET("test", HttpAnyHome)
	protected.GET("home", HttpAnyHome)

	unprotected := r.Group("u", HttpNoAuth())
	unprotected.GET("signin", HttpAnySignIn)
	unprotected.POST("signin", HttpAnySignIn)
	unprotected.GET("recover", HttpGetRecover)

	srv := &http.Server{
		Addr:    a.Addr,
		Handler: r,
	}

	go func() {
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			log.WithFields(log.Fields{"call": "http.ListenAndServe", "attr": a.Addr, "error": err}).Errorf("")
		}
	}()

	c := cron.New(cron.WithLocation(time.UTC))
	if _, err := c.AddFunc("0 * * * *", func() { cfg.Run() }); err != nil {
		log.WithFields(log.Fields{"call": "cron.AddFunc", "error": err}).Errorf("")
	}
	c.Start()
	log.WithFields(log.Fields{"call": "cron.Start"}).Debugf("cron started")

	// Listen for the interrupt signal.
	<-ctx.Done()

	// Restore default behavior on the interrupt signal and notify user of shutdown.
	stop()
	log.WithFields(log.Fields{"call": "stop"}).Warnf("shutting down")

	// The context is used to inform the server it has 5 seconds to finish
	// the request it is currently handling
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	if err := srv.Shutdown(ctx); err != nil {
		log.WithFields(log.Fields{"call": "http.Shutdown", "error": err}).Errorf("shutting down")
	}

}
