package middleware

import (
	"github.com/gofiber/fiber/v2"

	"wucher/internal/service"
	sharedconst "wucher/internal/shared/const"
	"wucher/internal/shared/pkg/apperrorsx"
)

const permissionPinVerificationHeader = "X-PIN-Verification-Token"

func PermissionRequired(svc *service.AuthService, permissionKey string) fiber.Handler {
	return permissionRequired(svc, permissionKey, true)
}

func PermissionRequiredReusable(svc *service.AuthService, permissionKey string) fiber.Handler {
	return permissionRequired(svc, permissionKey, false)
}

func permissionRequired(svc *service.AuthService, permissionKey string, consume bool) fiber.Handler {
	return func(c *fiber.Ctx) error {
		if svc == nil {
			e := apperrorsx.New(apperrorsx.ErrUserUnauthorized)
			e.Message = "auth service not configured"
			return apperrorsx.Write(c, e)
		}

		raw := c.Locals("user_id")
		userID, ok := raw.([]byte)
		if !ok || len(userID) == 0 {
			e := apperrorsx.New(apperrorsx.ErrUserUnauthorized)
			e.Message = "missing auth context"
			return apperrorsx.Write(c, e)
		}

		allowed, err := svc.HasPermission(c.UserContext(), userID, permissionKey)
		if err != nil {
			e := apperrorsx.New(apperrorsx.ErrForbiddenAccess)
			e.Message = "failed to verify permission"
			return apperrorsx.Write(c, e)
		}
		if !allowed {
			e := apperrorsx.New(apperrorsx.ErrForbiddenAccess)
			e.Message = "insufficient permission"
			return apperrorsx.Write(c, e)
		}

		required, err := svc.PermissionRequiresPIN(c.UserContext(), permissionKey)
		if err != nil {
			e := apperrorsx.New(apperrorsx.ErrForbiddenAccess)
			e.Message = "failed to verify PIN requirement"
			return apperrorsx.Write(c, e)
		}
		if required {
			action := sharedconst.PinActionForPermissionKey(permissionKey)
			if action == "" {
				action = permissionKey
			}
			token := c.Get(permissionPinVerificationHeader)
			sessionID := ""
			for _, accessToken := range service.CookieValues(c.Get(fiber.HeaderCookie), svc.AccessCookieName()) {
				sid, err := svc.ParseAccessTokenSessionID(c.UserContext(), accessToken)
				if err == nil {
					sessionID = sid
					break
				}
			}
			if sessionID == "" {
				return apperrorsx.Write(c, apperrorsx.New(apperrorsx.ErrTokenInvalid))
			}
			var verifyErr error
			if consume {
				verifyErr = svc.ConsumeSecurityPINActionTokenInSession(c.UserContext(), userID, action, token, sessionID)
			} else {
				verifyErr = svc.ValidateSecurityPINActionTokenInSession(c.UserContext(), userID, action, token, sessionID)
			}
			if verifyErr != nil {
				return apperrorsx.Write(c, apperrorsx.New(apperrorsx.ErrPINVerificationRequired202))
			}
		}
		return c.Next()
	}
}
