package middlewares

// |@@| C

import (
	"context"
	"gardening/src/lib"
	"gardening/src/lib/config"
	erro "gardening/src/lib/error"
	jwt "github.com/appleboy/gin-jwt/v2"
	"github.com/gin-gonic/gin"
	"net/http"
	"time"
)

var identityKey = "id"

type User struct {
	Id string
}

type login struct {
	Username string `form:"username" json:"username" binding:"required"`
	Password string `form:"password" json:"password" binding:"required"`
}

type JwtMiddleware struct {
	Config *config.Config
	Data   struct {
		Middleware *jwt.GinJWTMiddleware
	}
}

func (this *JwtMiddleware) Act() gin.HandlerFunc {
	this.checkInitialization()
	return this.Data.Middleware.MiddlewareFunc()
}

func (this *JwtMiddleware) Initialize(ctx context.Context) {
	if this.Data.Middleware != nil {
		panic(erro.N("JwtMiddleware has already been initialized"))
	}
	middleware, err := this.middleware(ctx)
	lib.Poe(err)
	err = middleware.MiddlewareInit()
	lib.Poe(err)
	this.Data.Middleware = middleware
}

func (this *JwtMiddleware) checkInitialization() {
	if this.Data.Middleware == nil {
		panic(erro.N("JwtMiddleware has not been initialized"))
	}
}

func (this *JwtMiddleware) getMiddleware() *jwt.GinJWTMiddleware {
	this.checkInitialization()
	return this.Data.Middleware
}

func (this *JwtMiddleware) RefreshRoute() gin.HandlerFunc {
	return this.getMiddleware().RefreshHandler
}

func (this *JwtMiddleware) LoginRoute() gin.HandlerFunc {
	return this.getMiddleware().LoginHandler
}

func (this *JwtMiddleware) LogoutRoute() gin.HandlerFunc {
	return this.getMiddleware().LogoutHandler
}

func (this *JwtMiddleware) middleware(ctx context.Context) (*jwt.GinJWTMiddleware, error) {
	return jwt.New(&jwt.GinJWTMiddleware{
		Realm:       "app zone",
		Key:         this.getSecretKeyFromEnv(),
		Timeout:     12 * time.Hour,
		MaxRefresh:  12 * time.Hour,
		IdentityKey: identityKey,
		PayloadFunc: func(data interface{}) jwt.MapClaims {
			if v, ok := data.(*User); ok {
				return jwt.MapClaims{
					identityKey: v.Id,
				}
			}
			return jwt.MapClaims{}
		},
		IdentityHandler: func(c *gin.Context) interface{} {
			claims := jwt.ExtractClaims(c)
			c.Set("identity_uid", claims[identityKey].(string))
			return &User{
				Id: claims[identityKey].(string),
			}
		},
		Authenticator: func(c *gin.Context) (interface{}, error) {
			var loginVals login
			if err := c.ShouldBind(&loginVals); err != nil {
				return "", jwt.ErrMissingLoginValues
			}
			userID := loginVals.Username
			password := loginVals.Password

			if userID == "admin" && password == this.getPasswordFromEnvValue() {
				return &User{
					Id: userID,
				}, nil
			}

			return nil, jwt.ErrFailedAuthentication
		},
		Authorizator: func(data interface{}, c *gin.Context) bool {
			if v, ok := data.(*User); ok && v.Id == "admin" {
				return true
			}
			return false
		},
		Unauthorized: func(c *gin.Context, code int, message string) {
			contentType := c.Request.Header.Get("Content-Type")
			if contentType == "" {
				c.Redirect(http.StatusFound, "/auth/app-login?message=unauthorized")
				return
			}
			c.JSON(code, gin.H{
				"code":    code,
				"message": message,
			})
		},
		TimeFunc: time.Now,
		LoginResponse: func(c *gin.Context, code int, message string, time time.Time) {
			contentType := c.Request.Header.Get("Content-Type")
			if contentType == "application/x-www-form-urlencoded" {
				c.Redirect(http.StatusFound, "/app/")
				return
			}
			c.JSON(http.StatusOK, gin.H{
				"code":    http.StatusOK,
				"message": message,
			})
		},
		LogoutResponse: func(c *gin.Context, code int) {
			c.Redirect(http.StatusFound, "/app")
		},
		SendCookie:     true,
		SecureCookie:   false, //non HTTPS dev environments
		CookieHTTPOnly: true,  // JS can't modify
		//CookieDomain:     "localhost:8080",
		CookieSameSite: http.SameSiteDefaultMode,
		CookieName:     this.getCookieName(ctx),
		TokenLookup:    "header: Authorization, query: token, cookie: " + this.getCookieName(ctx),
		TokenHeadName:  "Bearer",
	})
}

func (this *JwtMiddleware) getCookieName(ctx context.Context) string {
	if !this.Config.HasEnvValue(ctx, "JWT_COOKIE_NAME") {
		return "jwt"
	}
	return this.Config.GetEnvValue(ctx, "JWT_COOKIE_NAME")
}

func (this *JwtMiddleware) getSecretKeyFromEnv() []byte {
	value := this.Config.MustGetEnvValue(context.Background(), "JWT_SECRET_KEY")
	if value == "" {
		panic(erro.N("JWT_SECRET_KEY environment variable not set"))
	}
	return []byte(value)
}

func (this *JwtMiddleware) getPasswordFromEnvValue() string {
	return this.Config.MustGetEnvValue(context.Background(), "ADMIN_PASSWORD")
}
