package middleware

import (
	"crypto/subtle"
	"fmt"
	"rijig/utils"
	"time"

	"github.com/gofiber/fiber/v2"
	"github.com/gofiber/fiber/v2/middleware/logger"
)

type AuthConfig struct {
	RequiredTokenType  utils.TokenType
	RequiredRoles      []string
	RequiredStatuses   []string
	RequiredStep       int
	RequireComplete    bool
	SkipAuth           bool
	AllowPartialToken  bool
	CustomErrorHandler ErrorHandler
}

type ErrorHandler func(c *fiber.Ctx, err error) error

type AuthContext struct {
	Claims    *utils.JWTClaims
	StepInfo  *utils.RegistrationStepInfo
	IsAdmin   bool
	CanAccess bool
}

type AuthError struct {
	Code    string      `json:"error"`
	Message string      `json:"message"`
	Details interface{} `json:"details,omitempty"`
}

func (e *AuthError) Error() string {
	return e.Message
}

var (
	ErrMissingToken = &AuthError{
		Code:    "MISSING_TOKEN",
		Message: "Token akses diperlukan",
	}

	ErrInvalidTokenFormat = &AuthError{
		Code:    "INVALID_TOKEN_FORMAT",
		Message: "Format token tidak valid",
	}

	ErrInvalidToken = &AuthError{
		Code:    "INVALID_TOKEN",
		Message: "Token tidak valid atau telah kadaluarsa",
	}

	ErrUserContextNotFound = &AuthError{
		Code:    "USER_CONTEXT_NOT_FOUND",
		Message: "Silakan login terlebih dahulu",
	}

	ErrInsufficientPermissions = &AuthError{
		Code:    "INSUFFICIENT_PERMISSIONS",
		Message: "Akses ditolak untuk role ini",
	}

	ErrRegistrationIncomplete = &AuthError{
		Code:    "REGISTRATION_INCOMPLETE",
		Message: "Registrasi belum lengkap",
	}

	ErrRegistrationNotApproved = &AuthError{
		Code:    "REGISTRATION_NOT_APPROVED",
		Message: "Registrasi belum disetujui",
	}

	ErrInvalidTokenType = &AuthError{
		Code:    "INVALID_TOKEN_TYPE",
		Message: "Tipe token tidak sesuai",
	}

	ErrStepNotAccessible = &AuthError{
		Code:    "STEP_NOT_ACCESSIBLE",
		Message: "Step registrasi belum dapat diakses",
	}

	ErrAwaitingApproval = &AuthError{
		Code:    "AWAITING_ADMIN_APPROVAL",
		Message: "Menunggu persetujuan admin",
	}

	ErrInvalidRegistrationStatus = &AuthError{
		Code:    "INVALID_REGISTRATION_STATUS",
		Message: "Status registrasi tidak sesuai",
	}
)

func defaultErrorHandler(c *fiber.Ctx, err error) error {
	if authErr, ok := err.(*AuthError); ok {
		statusCode := getStatusCodeForError(authErr.Code)
		return c.Status(statusCode).JSON(authErr)
	}

	return c.Status(fiber.StatusInternalServerError).JSON(&AuthError{
		Code:    "INTERNAL_ERROR",
		Message: "Terjadi kesalahan internal",
	})
}

func getStatusCodeForError(errorCode string) int {
	switch errorCode {
	case "MISSING_TOKEN", "INVALID_TOKEN_FORMAT", "INVALID_TOKEN", "USER_CONTEXT_NOT_FOUND":
		return fiber.StatusUnauthorized
	case "INSUFFICIENT_PERMISSIONS", "REGISTRATION_INCOMPLETE", "REGISTRATION_NOT_APPROVED",
		"INVALID_TOKEN_TYPE", "STEP_NOT_ACCESSIBLE", "AWAITING_ADMIN_APPROVAL",
		"INVALID_REGISTRATION_STATUS":
		return fiber.StatusForbidden
	default:
		return fiber.StatusInternalServerError
	}
}

func AuthMiddleware(config ...AuthConfig) fiber.Handler {
	cfg := AuthConfig{}
	if len(config) > 0 {
		cfg = config[0]
	}

	if cfg.CustomErrorHandler == nil {
		cfg.CustomErrorHandler = defaultErrorHandler
	}

	return func(c *fiber.Ctx) error {

		if cfg.SkipAuth {
			return c.Next()
		}

		claims, err := extractAndValidateToken(c)
		if err != nil {
			return cfg.CustomErrorHandler(c, err)
		}

		authCtx := createAuthContext(claims)

		if err := validateAuthConfig(authCtx, cfg); err != nil {
			return cfg.CustomErrorHandler(c, err)
		}

		c.Locals("user", claims)
		c.Locals("auth_context", authCtx)

		return c.Next()
	}
}

func extractAndValidateToken(c *fiber.Ctx) (*utils.JWTClaims, error) {
	authHeader := c.Get("Authorization")
	if authHeader == "" {
		return nil, ErrMissingToken
	}

	token, err := utils.ExtractTokenFromHeader(authHeader)
	if err != nil {
		return nil, ErrInvalidTokenFormat
	}

	claims, err := utils.ValidateAccessToken(token)
	if err != nil {
		return nil, ErrInvalidToken
	}

	return claims, nil
}

func createAuthContext(claims *utils.JWTClaims) *AuthContext {
	stepInfo := utils.GetRegistrationStepInfo(
		claims.Role,
		claims.RegistrationProgress,
		claims.RegistrationStatus,
	)

	return &AuthContext{
		Claims:    claims,
		StepInfo:  stepInfo,
		IsAdmin:   claims.Role == utils.RoleAdministrator,
		CanAccess: stepInfo.IsAccessible,
	}
}

func validateAuthConfig(authCtx *AuthContext, cfg AuthConfig) error {
	claims := authCtx.Claims

	if cfg.RequiredTokenType != "" {
		if claims.TokenType != cfg.RequiredTokenType {
			return &AuthError{
				Code:    "INVALID_TOKEN_TYPE",
				Message: fmt.Sprintf("Endpoint memerlukan token type: %s", cfg.RequiredTokenType),
				Details: fiber.Map{
					"current_token_type":  claims.TokenType,
					"required_token_type": cfg.RequiredTokenType,
				},
			}
		}
	}

	if len(cfg.RequiredRoles) > 0 {
		if !contains(cfg.RequiredRoles, claims.Role) {
			return &AuthError{
				Code:    "INSUFFICIENT_PERMISSIONS",
				Message: "Akses ditolak untuk role ini",
				Details: fiber.Map{
					"user_role":     claims.Role,
					"allowed_roles": cfg.RequiredRoles,
				},
			}
		}
	}

	if len(cfg.RequiredStatuses) > 0 {
		if !contains(cfg.RequiredStatuses, claims.RegistrationStatus) {
			return &AuthError{
				Code:    "INVALID_REGISTRATION_STATUS",
				Message: "Status registrasi tidak sesuai",
				Details: fiber.Map{
					"current_status":   claims.RegistrationStatus,
					"allowed_statuses": cfg.RequiredStatuses,
					"next_step":        authCtx.StepInfo.Description,
				},
			}
		}
	}

	if cfg.RequiredStep > 0 {
		if claims.RegistrationProgress < cfg.RequiredStep {
			return &AuthError{
				Code:    "STEP_NOT_ACCESSIBLE",
				Message: "Step registrasi belum dapat diakses",
				Details: fiber.Map{
					"current_step":      claims.RegistrationProgress,
					"required_step":     cfg.RequiredStep,
					"current_step_info": authCtx.StepInfo.Description,
				},
			}
		}

		if authCtx.StepInfo.RequiresAdminApproval && !authCtx.CanAccess {
			return &AuthError{
				Code:    "AWAITING_ADMIN_APPROVAL",
				Message: "Menunggu persetujuan admin",
				Details: fiber.Map{
					"status": claims.RegistrationStatus,
				},
			}
		}
	}

	if cfg.RequireComplete {
		if claims.TokenType != utils.TokenTypeFull {
			return &AuthError{
				Code:    "REGISTRATION_INCOMPLETE",
				Message: "Registrasi belum lengkap",
				Details: fiber.Map{
					"registration_status":     claims.RegistrationStatus,
					"registration_progress":   claims.RegistrationProgress,
					"next_step":               authCtx.StepInfo.Description,
					"requires_admin_approval": authCtx.StepInfo.RequiresAdminApproval,
					"can_proceed":             authCtx.CanAccess,
				},
			}
		}

		if !utils.IsRegistrationComplete(claims.RegistrationStatus) {
			return ErrRegistrationNotApproved
		}
	}

	return nil
}

func contains(slice []string, item string) bool {
	for _, s := range slice {
		if s == item {
			return true
		}
	}
	return false
}

func RequireAuth() fiber.Handler {
	return AuthMiddleware()
}

func RequireFullToken() fiber.Handler {
	return AuthMiddleware(AuthConfig{
		RequiredTokenType: utils.TokenTypeFull,
		RequireComplete:   true,
	})
}

func RequirePartialToken() fiber.Handler {
	return AuthMiddleware(AuthConfig{
		RequiredTokenType: utils.TokenTypePartial,
	})
}

func RequireRoles(roles ...string) fiber.Handler {
	return AuthMiddleware(AuthConfig{
		RequiredRoles: roles,
	})
}

func RequireAdminRole() fiber.Handler {
	return RequireRoles(utils.RoleAdministrator)
}

func RequireRegistrationStep(step int) fiber.Handler {
	return AuthMiddleware(AuthConfig{
		RequiredStep: step,
	})
}

func RequireRegistrationStatus(statuses ...string) fiber.Handler {
	return AuthMiddleware(AuthConfig{
		RequiredStatuses: statuses,
	})
}

func RequireTokenType(tokenType utils.TokenType) fiber.Handler {
	return AuthMiddleware(AuthConfig{
		RequiredTokenType: tokenType,
	})
}

func RequireCompleteRegistrationForRole(roles ...string) fiber.Handler {
	return func(c *fiber.Ctx) error {
		claims, err := GetUserFromContext(c)
		if err != nil {
			return err
		}

		if contains(roles, claims.Role) {
			return RequireFullToken()(c)
		}

		return c.Next()
	}
}

func RequireRoleAndComplete(roles ...string) fiber.Handler {
	return AuthMiddleware(AuthConfig{
		RequiredRoles:   roles,
		RequireComplete: true,
	})
}

func RequireRoleAndStep(step int, roles ...string) fiber.Handler {
	return AuthMiddleware(AuthConfig{
		RequiredRoles: roles,
		RequiredStep:  step,
	})
}

func RequireRoleAndStatus(statuses []string, roles ...string) fiber.Handler {
	return AuthMiddleware(AuthConfig{
		RequiredRoles:    roles,
		RequiredStatuses: statuses,
	})
}

func GetUserFromContext(c *fiber.Ctx) (*utils.JWTClaims, error) {
	claims, ok := c.Locals("user").(*utils.JWTClaims)
	if !ok {
		return nil, ErrUserContextNotFound
	}
	return claims, nil
}

func GetAuthContextFromContext(c *fiber.Ctx) (*AuthContext, error) {
	authCtx, ok := c.Locals("auth_context").(*AuthContext)
	if !ok {

		claims, err := GetUserFromContext(c)
		if err != nil {
			return nil, err
		}
		return createAuthContext(claims), nil
	}
	return authCtx, nil
}

func MustGetUserFromContext(c *fiber.Ctx) *utils.JWTClaims {
	claims, err := GetUserFromContext(c)
	if err != nil {
		panic("user context not found")
	}
	return claims
}

func GetUserID(c *fiber.Ctx) (string, error) {
	claims, err := GetUserFromContext(c)
	if err != nil {
		return "", err
	}
	return claims.UserID, nil
}

func GetUserRole(c *fiber.Ctx) (string, error) {
	claims, err := GetUserFromContext(c)
	if err != nil {
		return "", err
	}
	return claims.Role, nil
}

func IsAdmin(c *fiber.Ctx) bool {
	claims, err := GetUserFromContext(c)
	if err != nil {
		return false
	}
	return claims.Role == utils.RoleAdministrator
}

func IsRegistrationComplete(c *fiber.Ctx) bool {
	claims, err := GetUserFromContext(c)
	if err != nil {
		return false
	}
	return utils.IsRegistrationComplete(claims.RegistrationStatus)
}

func HasRole(c *fiber.Ctx, role string) bool {
	claims, err := GetUserFromContext(c)
	if err != nil {
		return false
	}
	return claims.Role == role
}

func HasAnyRole(c *fiber.Ctx, roles ...string) bool {
	claims, err := GetUserFromContext(c)
	if err != nil {
		return false
	}
	return contains(roles, claims.Role)
}

type RateLimitConfig struct {
	MaxRequests int
	Window      time.Duration
	KeyFunc     func(*fiber.Ctx) string
	SkipFunc    func(*fiber.Ctx) bool
}

func AuthRateLimit(config RateLimitConfig) fiber.Handler {
	if config.KeyFunc == nil {
		config.KeyFunc = func(c *fiber.Ctx) string {
			claims, err := GetUserFromContext(c)
			if err != nil {
				return c.IP()
			}
			return fmt.Sprintf("user:%s", claims.UserID)
		}
	}

	return func(c *fiber.Ctx) error {
		if config.SkipFunc != nil && config.SkipFunc(c) {
			return c.Next()
		}

		key := fmt.Sprintf("rate_limit:%s", config.KeyFunc(c))

		var count int
		err := utils.GetCache(key, &count)
		if err != nil {
			count = 0
		}

		if count >= config.MaxRequests {
			return c.Status(fiber.StatusTooManyRequests).JSON(&AuthError{
				Code:    "RATE_LIMIT_EXCEEDED",
				Message: "Terlalu banyak permintaan, coba lagi nanti",
				Details: fiber.Map{
					"max_requests": config.MaxRequests,
					"window":       config.Window.String(),
				},
			})
		}

		count++
		utils.SetCache(key, count, config.Window)

		return c.Next()
	}
}

func DeviceValidation() fiber.Handler {
	return func(c *fiber.Ctx) error {
		claims, err := GetUserFromContext(c)
		if err != nil {
			return err
		}

		deviceID := c.Get("X-Device-ID")
		if deviceID == "" {
			return c.Status(fiber.StatusBadRequest).JSON(&AuthError{
				Code:    "MISSING_DEVICE_ID",
				Message: "Device ID diperlukan",
			})
		}

		if subtle.ConstantTimeCompare([]byte(claims.DeviceID), []byte(deviceID)) != 1 {
			return c.Status(fiber.StatusForbidden).JSON(&AuthError{
				Code:    "DEVICE_MISMATCH",
				Message: "Device tidak cocok dengan token",
			})
		}

		return c.Next()
	}
}

func SessionValidation() fiber.Handler {
	return func(c *fiber.Ctx) error {
		claims, err := GetUserFromContext(c)
		if err != nil {
			return err
		}

		sessionKey := fmt.Sprintf("session:%s", claims.SessionID)
		var sessionData interface{}
		err = utils.GetCache(sessionKey, &sessionData)
		if err != nil {
			return c.Status(fiber.StatusUnauthorized).JSON(&AuthError{
				Code:    "SESSION_EXPIRED",
				Message: "Sesi telah berakhir, silakan login kembali",
			})
		}

		return c.Next()
	}
}

func AuthLogger() fiber.Handler {
	return logger.New(logger.Config{
		Format: "[${time}] ${status} - ${method} ${path} - User: ${locals:user_id} Role: ${locals:user_role} IP: ${ip}\n",
		CustomTags: map[string]logger.LogFunc{
			"user_id": func(output logger.Buffer, c *fiber.Ctx, data *logger.Data, extraParam string) (int, error) {
				if claims, err := GetUserFromContext(c); err == nil {
					return output.WriteString(claims.UserID)
				}
				return output.WriteString("anonymous")
			},
			"user_role": func(output logger.Buffer, c *fiber.Ctx, data *logger.Data, extraParam string) (int, error) {
				if claims, err := GetUserFromContext(c); err == nil {
					return output.WriteString(claims.Role)
				}
				return output.WriteString("none")
			},
		},
	})
}
