package auth

import (
	"context"
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"strings"
	"time"

	"github.com/bigtux/ophion/internal/security"
	"github.com/gofiber/fiber/v2"
	"github.com/golang-jwt/jwt/v5"
	"github.com/redis/go-redis/v9"
)

// ═══════════════════════════════════════════════════════════
// 🔐 AUTH CONFIG
// ═══════════════════════════════════════════════════════════

type AuthConfig struct {
	JWTSecret        []byte
	JWTExpiration    time.Duration
	RefreshExpiration time.Duration
	Issuer           string
}

type AuthService struct {
	config        AuthConfig
	redis         *redis.Client
	rateLimiter   *security.RateLimiter
	loginTracker  *security.LoginAttemptTracker
	apiKeyStore   APIKeyStore
}

// APIKeyStore interface para storage de API keys
type APIKeyStore interface {
	ValidateKey(ctx context.Context, keyHash string) (*APIKeyInfo, error)
	UpdateLastUsed(ctx context.Context, keyID string) error
}

type APIKeyInfo struct {
	ID     string
	OrgID  string
	Scopes []string
	Name   string
}

// NewAuthService cria serviço de autenticação
func NewAuthService(config AuthConfig, redis *redis.Client, apiKeyStore APIKeyStore) *AuthService {
	return &AuthService{
		config:       config,
		redis:        redis,
		rateLimiter:  security.NewRateLimiter(security.AuthRateLimit, time.Minute),
		loginTracker: security.NewLoginAttemptTracker(),
		apiKeyStore:  apiKeyStore,
	}
}

// ═══════════════════════════════════════════════════════════
// 🎫 JWT TOKENS
// ═══════════════════════════════════════════════════════════

type TokenClaims struct {
	jwt.RegisteredClaims
	UserID   string   `json:"uid"`
	OrgID    string   `json:"oid"`
	Email    string   `json:"email"`
	Role     string   `json:"role"`
	Scopes   []string `json:"scopes,omitempty"`
	TokenID  string   `json:"jti"`
}

// GenerateTokenPair gera access + refresh tokens
func (s *AuthService) GenerateTokenPair(userID, orgID, email, role string) (accessToken, refreshToken string, err error) {
	now := time.Now()
	tokenID := generateTokenID()
	
	// Access Token (curta duração)
	accessClaims := TokenClaims{
		RegisteredClaims: jwt.RegisteredClaims{
			Issuer:    s.config.Issuer,
			Subject:   userID,
			IssuedAt:  jwt.NewNumericDate(now),
			ExpiresAt: jwt.NewNumericDate(now.Add(s.config.JWTExpiration)),
			ID:        tokenID,
		},
		UserID:  userID,
		OrgID:   orgID,
		Email:   email,
		Role:    role,
		TokenID: tokenID,
	}
	
	accessToken, err = jwt.NewWithClaims(jwt.SigningMethodHS256, accessClaims).SignedString(s.config.JWTSecret)
	if err != nil {
		return "", "", fmt.Errorf("failed to sign access token: %w", err)
	}
	
	// Refresh Token (longa duração)
	refreshID := generateTokenID()
	refreshClaims := jwt.RegisteredClaims{
		Issuer:    s.config.Issuer,
		Subject:   userID,
		IssuedAt:  jwt.NewNumericDate(now),
		ExpiresAt: jwt.NewNumericDate(now.Add(s.config.RefreshExpiration)),
		ID:        refreshID,
	}
	
	refreshToken, err = jwt.NewWithClaims(jwt.SigningMethodHS256, refreshClaims).SignedString(s.config.JWTSecret)
	if err != nil {
		return "", "", fmt.Errorf("failed to sign refresh token: %w", err)
	}
	
	// Armazenar refresh token no Redis (permite revogação)
	if s.redis != nil {
		ctx := context.Background()
		key := fmt.Sprintf("refresh_token:%s", refreshID)
		s.redis.Set(ctx, key, userID, s.config.RefreshExpiration)
	}
	
	return accessToken, refreshToken, nil
}

// ValidateAccessToken valida access token
func (s *AuthService) ValidateAccessToken(tokenString string) (*TokenClaims, error) {
	token, err := jwt.ParseWithClaims(tokenString, &TokenClaims{}, func(token *jwt.Token) (interface{}, error) {
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
		}
		return s.config.JWTSecret, nil
	})
	
	if err != nil {
		return nil, fmt.Errorf("invalid token: %w", err)
	}
	
	claims, ok := token.Claims.(*TokenClaims)
	if !ok || !token.Valid {
		return nil, fmt.Errorf("invalid token claims")
	}
	
	// Verificar se token foi revogado
	if s.redis != nil {
		ctx := context.Background()
		revoked, _ := s.redis.Get(ctx, fmt.Sprintf("revoked_token:%s", claims.TokenID)).Result()
		if revoked != "" {
			return nil, fmt.Errorf("token revoked")
		}
	}
	
	return claims, nil
}

// RevokeToken revoga um token
func (s *AuthService) RevokeToken(tokenID string, expiration time.Duration) error {
	if s.redis == nil {
		return nil
	}
	ctx := context.Background()
	return s.redis.Set(ctx, fmt.Sprintf("revoked_token:%s", tokenID), "1", expiration).Err()
}

// ═══════════════════════════════════════════════════════════
// 🛡️ MIDDLEWARE
// ═══════════════════════════════════════════════════════════

// AuthMiddleware middleware de autenticação
func (s *AuthService) AuthMiddleware() fiber.Handler {
	return func(c *fiber.Ctx) error {
		// Obter IP real (considerando proxies)
		ip := c.IP()
		if forwarded := c.Get("X-Forwarded-For"); forwarded != "" {
			ip = strings.Split(forwarded, ",")[0]
		}
		
		// Rate limiting por IP
		if !s.rateLimiter.Allow(ip) {
			return c.Status(429).JSON(fiber.Map{
				"error":   "Too many requests",
				"message": "Rate limit exceeded. Try again later.",
			})
		}
		
		authHeader := c.Get("Authorization")
		if authHeader == "" {
			return c.Status(401).JSON(fiber.Map{
				"error":   "Unauthorized",
				"message": "Missing authorization header",
			})
		}
		
		token := strings.TrimPrefix(authHeader, "Bearer ")
		
		// API Key authentication
		if strings.HasPrefix(token, security.APIKeyPrefix) {
			return s.authenticateAPIKey(c, token)
		}
		
		// JWT authentication
		return s.authenticateJWT(c, token)
	}
}

// authenticateAPIKey valida API key
func (s *AuthService) authenticateAPIKey(c *fiber.Ctx, apiKey string) error {
	// Validar formato
	if !security.ValidateAPIKeyFormat(apiKey) {
		return c.Status(401).JSON(fiber.Map{
			"error":   "Unauthorized",
			"message": "Invalid API key format",
		})
	}
	
	// Hash da key para busca
	keyHash := security.HashAPIKey(apiKey)
	
	// Buscar no storage
	keyInfo, err := s.apiKeyStore.ValidateKey(c.Context(), keyHash)
	if err != nil || keyInfo == nil {
		return c.Status(401).JSON(fiber.Map{
			"error":   "Unauthorized",
			"message": "Invalid API key",
		})
	}
	
	// Atualizar last_used (async)
	go s.apiKeyStore.UpdateLastUsed(context.Background(), keyInfo.ID)
	
	// Setar contexto
	c.Locals("auth_type", "api_key")
	c.Locals("org_id", keyInfo.OrgID)
	c.Locals("api_key_id", keyInfo.ID)
	c.Locals("scopes", keyInfo.Scopes)
	
	return c.Next()
}

// authenticateJWT valida JWT token
func (s *AuthService) authenticateJWT(c *fiber.Ctx, token string) error {
	claims, err := s.ValidateAccessToken(token)
	if err != nil {
		return c.Status(401).JSON(fiber.Map{
			"error":   "Unauthorized",
			"message": err.Error(),
		})
	}
	
	// Setar contexto
	c.Locals("auth_type", "jwt")
	c.Locals("user_id", claims.UserID)
	c.Locals("org_id", claims.OrgID)
	c.Locals("email", claims.Email)
	c.Locals("role", claims.Role)
	c.Locals("token_id", claims.TokenID)
	
	return c.Next()
}

// RequireScopes middleware que exige scopes específicos
func RequireScopes(required ...string) fiber.Handler {
	return func(c *fiber.Ctx) error {
		scopes, ok := c.Locals("scopes").([]string)
		if !ok {
			// JWT tokens têm acesso total por padrão
			if c.Locals("auth_type") == "jwt" {
				return c.Next()
			}
			return c.Status(403).JSON(fiber.Map{
				"error":   "Forbidden",
				"message": "Missing required scopes",
			})
		}
		
		scopeMap := make(map[string]bool)
		for _, s := range scopes {
			scopeMap[s] = true
		}
		
		for _, req := range required {
			if !scopeMap[req] && !scopeMap["*"] {
				return c.Status(403).JSON(fiber.Map{
					"error":   "Forbidden",
					"message": fmt.Sprintf("Missing required scope: %s", req),
				})
			}
		}
		
		return c.Next()
	}
}

// RequireRole middleware que exige role específico
func RequireRole(roles ...string) fiber.Handler {
	return func(c *fiber.Ctx) error {
		userRole, ok := c.Locals("role").(string)
		if !ok {
			return c.Status(403).JSON(fiber.Map{
				"error":   "Forbidden",
				"message": "Role not found",
			})
		}
		
		for _, r := range roles {
			if userRole == r {
				return c.Next()
			}
		}
		
		// Admin tem acesso a tudo
		if userRole == "admin" {
			return c.Next()
		}
		
		return c.Status(403).JSON(fiber.Map{
			"error":   "Forbidden",
			"message": "Insufficient permissions",
		})
	}
}

// ═══════════════════════════════════════════════════════════
// 🔐 LOGIN PROTECTION
// ═══════════════════════════════════════════════════════════

// CheckLoginAllowed verifica se login é permitido (brute force protection)
func (s *AuthService) CheckLoginAllowed(identifier string) (bool, time.Duration, error) {
	locked, remaining := s.loginTracker.IsLocked(identifier)
	return !locked, remaining, nil
}

// RecordLoginAttempt registra tentativa de login
func (s *AuthService) RecordLoginAttempt(identifier string, success bool) (blocked bool, remaining int) {
	if success {
		s.loginTracker.RecordSuccess(identifier)
		return false, 0
	}
	return s.loginTracker.RecordFailure(identifier)
}

// ═══════════════════════════════════════════════════════════
// 🔧 HELPERS
// ═══════════════════════════════════════════════════════════

func generateTokenID() string {
	bytes := make([]byte, 16)
	rand.Read(bytes)
	return hex.EncodeToString(bytes)
}

// GetUserID obtém user ID do contexto
func GetUserID(c *fiber.Ctx) string {
	if id, ok := c.Locals("user_id").(string); ok {
		return id
	}
	return ""
}

// GetOrgID obtém org ID do contexto
func GetOrgID(c *fiber.Ctx) string {
	if id, ok := c.Locals("org_id").(string); ok {
		return id
	}
	return ""
}

// GetRole obtém role do contexto
func GetRole(c *fiber.Ctx) string {
	if role, ok := c.Locals("role").(string); ok {
		return role
	}
	return ""
}
