package rest

import (
	"errors"
	"net/http"
	"strings"

	"github.com/gin-gonic/gin"
	"github.com/rs/zerolog/log"

	"quantex.com/qfixpt/src/app"
	jwttoken "quantex.com/qfixpt/src/common/jwttoken"
	"quantex.com/qfixpt/src/common/tracerr"
)

const ErrorField = "error"

func (cont *Controller) PartyAdmin(c *gin.Context) {
	if cont.GetUser(c).IsPartyAdmin {
		return
	}

	c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ErrorField: "Unauthorized Admin"})
}

func (cont *Controller) CanSendOrder(c *gin.Context) {
	if cont.GetUser(c).IsViewer {
		c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ErrorField: "Rol Viewer is unauthorized to send orders"})
	}
}

func (cont *Controller) Middleman(c *gin.Context) {
	if cont.GetUser(c).IsMiddleman {
		return
	}

	c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ErrorField: "Unauthorized Middleman"})
}

func (cont *Controller) BackOfficeUser(c *gin.Context) {
	if cont.GetUser(c).IsBackOffice {
		return
	}

	c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ErrorField: "Unauthorized User"})
}

func (cont *Controller) SuperUser(c *gin.Context) {
	if cont.GetUser(c).IsSuperUser {
		return
	}

	c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ErrorField: "Unauthorized Admin User"})
}

func (cont *Controller) Options(c *gin.Context) {
	if c.Request.Method != http.MethodOptions {
		c.Next()
	} else {
		setHeaders(c, cont.config)
		c.AbortWithStatus(http.StatusOK)
	}
}

func (cont *Controller) AuthRequired(ctx *gin.Context) {
	setHeaders(ctx, cont.config)

	if c, err := ctx.Cookie("session_token"); c != "" && err == nil {
		cont.SessionCookieAuth(ctx)

		return
	}

	// check header for Token Auth
	reqToken := ctx.GetHeader("Authorization")
	if reqToken != "" {
		cont.AuthorizationAuth(reqToken, ctx)

		return
	}

	log.Error().Msg("Token Auth Unauthorized: missing session cookie and Authorization header")

	ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ErrorField: "Unauthorized - AuthRequired 1"})
}

func (cont *Controller) AuthorizationAuth(reqToken string, ctx *gin.Context) {
	token := strings.TrimSpace(strings.TrimPrefix(reqToken, "Bearer"))

	if cont.config.EnableJWTAuth && jwttoken.IsJWT(token) {
		cont.JWTTokenAuth(token, ctx)

		return
	}

	cont.BearerTokenAuth(token, ctx)
}

func (cont *Controller) BearerTokenAuth(reqToken string, ctx *gin.Context) {
	if !strings.HasPrefix(reqToken, "Bearer ") {
		log.Error().Msg("Token Auth Unauthorized: missing Bearer prefix")

		ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ErrorField: "Token Auth Unauthorized"})

		return
	}

	token := strings.Split(reqToken, "Bearer ")
	if len(token) != 2 {

		log.Error().Msg("Token Auth Unauthorized at TokenAuth: invalid token format")

		ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ErrorField: "Token Auth Unauthorized 1"})

		return
	}

	user, err := cont.validateUserToken(token[1])
	if err != nil {
		err = errors.New("Token Auth Unauthorized at TokenAuth - %s" + err.Error())

		log.Error().Msg(err.Error())

		ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ErrorField: "Token Auth Unauthorized 2"})

		return
	}

	log.Info().Msgf("User %s authenticated successfully at TokenAuth", user.Email)

	ctx.Set(responseKey, []byte(user.Email))

	ctx.Next()
}

func (cont *Controller) JWTTokenAuth(token string, ctx *gin.Context) {
	from := ctx.Query("from")

	serviceAuth, err := jwttoken.Validate(from, token, cont.config.AuthorizedServices)
	if err != nil || serviceAuth == nil || serviceAuth.Token == nil {
		err := tracerr.Errorf("invalid token or claims: %w", err)

		log.Error().Msg(err.Error())

		ctx.AbortWithStatusJSON(http.StatusUnauthorized,
			gin.H{ErrorField: err.Error()})

		return
	}

	user, err := cont.validateUserToken(*serviceAuth.Token)
	if err != nil {
		err = tracerr.Errorf("Token Auth Unauthorized at JWTTokenAuth: %w", err)

		log.Error().Msg(err.Error())

		ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ErrorField: "Invalid credentials"})

		return
	}

	ctx.Set("authorized_service", serviceAuth)
	ctx.Set(responseKey, []byte(user.Email)) // TODO: will services be treated as users? if not remove this line

	log.Info().Str("issuer", serviceAuth.Name).Msg("Service authenticated successfully")

	ctx.Next()
}

func (cont *Controller) validateUserToken(token string) (user *app.User, err error) {
	userInfo := strings.Split(token, ":")
	if len(userInfo) != 2 || userInfo[1] == "" {
		err = tracerr.Errorf("invalid token format at validateUserToken")

		log.Error().Msg(err.Error())

		return nil, err
	}

	email := userInfo[0]

	user, err = cont.store.UserByEmail(email)
	if user == nil || err != nil {
		err = tracerr.Errorf("user not found at validateUserToken: %w", err)

		log.Error().Msg(err.Error())

		return nil, err
	}

	tkn := userInfo[1]

	if user.Token != tkn {
		err = tracerr.Errorf("invalid token credentials at validateUserToken")

		log.Error().Msg(err.Error())

		return nil, err
	}

	log.Info().Str("email", user.Email).Msg("Service user validated successfully")

	return user, nil
}

func (cont *Controller) SessionCookieAuth(ctx *gin.Context) {
	// We can obtain the session token from the requests cookies, which come with every handler
	sessionToken, err := ctx.Cookie("session_token")
	if err != nil {
		if errors.Is(err, http.ErrNoCookie) {
			// If the cookie is not set, return an unauthorized status
			ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ErrorField: "Unauthorized - AuthRequired 1"})

			return
		}
		// For any other type of error, return a bad handler status
		ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{ErrorField: "Bad handler - AuthRequired 2"})

		return
	}

	cont.validateCookieToExternal(sessionToken, ctx)
}

func (cont *Controller) validateCookieToExternal(sessionToken string, ctx *gin.Context) {
	ok, err := cont.store.ValidateSession(sessionToken)
	if err != nil {
		ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ErrorField: "Unauthorized  - AuthRequired 4"})

		return
	}

	if !ok {
		// If the session token is not valid, return an unauthorized error
		ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ErrorField: "Unauthorized - AuthRequired 5"})

		return
	}

	ctx.Next()
}

func IPWhiteList(whitelist map[string]bool) gin.HandlerFunc {
	return func(c *gin.Context) {
		if !whitelist[c.ClientIP()] {
			c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
				"status":  http.StatusForbidden,
				"message": "Permission denied",
			})

			return
		}
	}
}
