package security

import (
	"crypto/rand"
	"crypto/sha256"
	"crypto/subtle"
	"encoding/base64"
	"encoding/hex"
	"fmt"
	"net"
	"regexp"
	"strings"
	"sync"
	"time"

	"github.com/gofiber/fiber/v2"
	"golang.org/x/crypto/bcrypt"
)

// ═══════════════════════════════════════════════════════════
// 🔐 CONSTANTES DE SEGURANÇA
// ═══════════════════════════════════════════════════════════

const (
	// Bcrypt cost (12-14 recomendado para produção)
	BcryptCost = 12

	// Tamanho mínimo de senha
	MinPasswordLength = 12

	// Tamanho da API Key
	APIKeyLength = 32

	// Prefixo da API Key
	APIKeyPrefix = "ophion_"

	// Rate limit padrão (requests por minuto)
	DefaultRateLimit = 100

	// Rate limit para auth (tentativas por minuto)
	AuthRateLimit = 5

	// Tempo de bloqueio após falhas (minutos)
	LockoutDuration = 15

	// Máximo de tentativas antes do bloqueio
	MaxFailedAttempts = 5
)

// ═══════════════════════════════════════════════════════════
// 🔑 API KEY MANAGEMENT
// ═══════════════════════════════════════════════════════════

// GenerateAPIKey cria uma nova API key segura
func GenerateAPIKey() (key string, hash string, prefix string) {
	bytes := make([]byte, APIKeyLength)
	if _, err := rand.Read(bytes); err != nil {
		panic("failed to generate random bytes")
	}

	key = APIKeyPrefix + hex.EncodeToString(bytes)
	hash = HashAPIKey(key)
	prefix = key[:len(APIKeyPrefix)+8] // ophion_xxxxxxxx

	return key, hash, prefix
}

// HashAPIKey cria hash SHA256 da API key
func HashAPIKey(key string) string {
	hash := sha256.Sum256([]byte(key))
	return hex.EncodeToString(hash[:])
}

// ValidateAPIKeyFormat verifica formato da API key
func ValidateAPIKeyFormat(key string) bool {
	if !strings.HasPrefix(key, APIKeyPrefix) {
		return false
	}
	// ophion_ + 64 hex chars
	return len(key) == len(APIKeyPrefix)+64
}

// SecureCompare compara strings em tempo constante (previne timing attacks)
func SecureCompare(a, b string) bool {
	return subtle.ConstantTimeCompare([]byte(a), []byte(b)) == 1
}

// ═══════════════════════════════════════════════════════════
// 🔒 PASSWORD MANAGEMENT
// ═══════════════════════════════════════════════════════════

// PasswordPolicy define regras de senha
type PasswordPolicy struct {
	MinLength        int
	RequireUppercase bool
	RequireLowercase bool
	RequireNumbers   bool
	RequireSpecial   bool
}

// DefaultPasswordPolicy retorna política padrão
func DefaultPasswordPolicy() PasswordPolicy {
	return PasswordPolicy{
		MinLength:        12,
		RequireUppercase: true,
		RequireLowercase: true,
		RequireNumbers:   true,
		RequireSpecial:   true,
	}
}

// ValidatePassword verifica se senha atende à política
func ValidatePassword(password string, policy PasswordPolicy) (bool, []string) {
	var errors []string

	if len(password) < policy.MinLength {
		errors = append(errors, fmt.Sprintf("Senha deve ter no mínimo %d caracteres", policy.MinLength))
	}

	if policy.RequireUppercase && !regexp.MustCompile(`[A-Z]`).MatchString(password) {
		errors = append(errors, "Senha deve conter letra maiúscula")
	}

	if policy.RequireLowercase && !regexp.MustCompile(`[a-z]`).MatchString(password) {
		errors = append(errors, "Senha deve conter letra minúscula")
	}

	if policy.RequireNumbers && !regexp.MustCompile(`[0-9]`).MatchString(password) {
		errors = append(errors, "Senha deve conter número")
	}

	if policy.RequireSpecial && !regexp.MustCompile(`[!@#$%^&*(),.?":{}|<>]`).MatchString(password) {
		errors = append(errors, "Senha deve conter caractere especial")
	}

	// Verificar senhas comuns
	if isCommonPassword(password) {
		errors = append(errors, "Senha muito comum, escolha outra")
	}

	return len(errors) == 0, errors
}

// HashPassword cria hash bcrypt da senha
func HashPassword(password string) (string, error) {
	bytes, err := bcrypt.GenerateFromPassword([]byte(password), BcryptCost)
	return string(bytes), err
}

// VerifyPassword verifica senha contra hash
func VerifyPassword(password, hash string) bool {
	err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
	return err == nil
}

// isCommonPassword verifica senhas comuns
func isCommonPassword(password string) bool {
	common := []string{
		"password", "123456", "12345678", "qwerty", "abc123",
		"monkey", "1234567", "letmein", "trustno1", "dragon",
		"baseball", "iloveyou", "master", "sunshine", "ashley",
		"passw0rd", "shadow", "123123", "654321", "superman",
		"senha", "mudar123", "admin123", "root123",
	}
	lower := strings.ToLower(password)
	for _, p := range common {
		if strings.Contains(lower, p) {
			return true
		}
	}
	return false
}

// ═══════════════════════════════════════════════════════════
// 🚦 RATE LIMITING
// ═══════════════════════════════════════════════════════════

// RateLimiter controla taxa de requests
type RateLimiter struct {
	sync.RWMutex
	requests map[string]*rateLimitEntry
	limit    int
	window   time.Duration
}

type rateLimitEntry struct {
	count     int
	firstSeen time.Time
	blocked   bool
	blockedAt time.Time
}

// NewRateLimiter cria novo rate limiter
func NewRateLimiter(limit int, window time.Duration) *RateLimiter {
	rl := &RateLimiter{
		requests: make(map[string]*rateLimitEntry),
		limit:    limit,
		window:   window,
	}
	// Cleanup goroutine
	go rl.cleanup()
	return rl
}

// Allow verifica se request é permitido
func (rl *RateLimiter) Allow(identifier string) bool {
	rl.Lock()
	defer rl.Unlock()

	now := time.Now()
	entry, exists := rl.requests[identifier]

	if !exists {
		rl.requests[identifier] = &rateLimitEntry{
			count:     1,
			firstSeen: now,
		}
		return true
	}

	// Verificar se está bloqueado
	if entry.blocked {
		if now.Sub(entry.blockedAt) < time.Duration(LockoutDuration)*time.Minute {
			return false
		}
		// Desbloquear
		entry.blocked = false
		entry.count = 0
		entry.firstSeen = now
	}

	// Verificar janela de tempo
	if now.Sub(entry.firstSeen) > rl.window {
		entry.count = 1
		entry.firstSeen = now
		return true
	}

	entry.count++

	if entry.count > rl.limit {
		return false
	}

	return true
}

// Block bloqueia um identificador
func (rl *RateLimiter) Block(identifier string) {
	rl.Lock()
	defer rl.Unlock()

	entry, exists := rl.requests[identifier]
	if !exists {
		entry = &rateLimitEntry{}
		rl.requests[identifier] = entry
	}
	entry.blocked = true
	entry.blockedAt = time.Now()
}

// cleanup remove entradas antigas
func (rl *RateLimiter) cleanup() {
	ticker := time.NewTicker(5 * time.Minute)
	for range ticker.C {
		rl.Lock()
		now := time.Now()
		for id, entry := range rl.requests {
			if now.Sub(entry.firstSeen) > rl.window*2 && !entry.blocked {
				delete(rl.requests, id)
			}
		}
		rl.Unlock()
	}
}

// ═══════════════════════════════════════════════════════════
// 🛡️ BRUTE FORCE PROTECTION
// ═══════════════════════════════════════════════════════════

// LoginAttemptTracker rastreia tentativas de login
type LoginAttemptTracker struct {
	sync.RWMutex
	attempts map[string]*loginAttempt
}

type loginAttempt struct {
	failures    int
	lastAttempt time.Time
	lockedUntil time.Time
}

// NewLoginAttemptTracker cria tracker
func NewLoginAttemptTracker() *LoginAttemptTracker {
	return &LoginAttemptTracker{
		attempts: make(map[string]*loginAttempt),
	}
}

// RecordFailure registra falha de login
func (t *LoginAttemptTracker) RecordFailure(identifier string) (blocked bool, remainingAttempts int) {
	t.Lock()
	defer t.Unlock()

	now := time.Now()
	attempt, exists := t.attempts[identifier]

	if !exists {
		t.attempts[identifier] = &loginAttempt{
			failures:    1,
			lastAttempt: now,
		}
		return false, MaxFailedAttempts - 1
	}

	// Reset se última tentativa foi há muito tempo
	if now.Sub(attempt.lastAttempt) > time.Duration(LockoutDuration)*time.Minute {
		attempt.failures = 1
		attempt.lastAttempt = now
		attempt.lockedUntil = time.Time{}
		return false, MaxFailedAttempts - 1
	}

	attempt.failures++
	attempt.lastAttempt = now

	if attempt.failures >= MaxFailedAttempts {
		attempt.lockedUntil = now.Add(time.Duration(LockoutDuration) * time.Minute)
		return true, 0
	}

	return false, MaxFailedAttempts - attempt.failures
}

// RecordSuccess registra sucesso de login
func (t *LoginAttemptTracker) RecordSuccess(identifier string) {
	t.Lock()
	defer t.Unlock()
	delete(t.attempts, identifier)
}

// IsLocked verifica se está bloqueado
func (t *LoginAttemptTracker) IsLocked(identifier string) (bool, time.Duration) {
	t.RLock()
	defer t.RUnlock()

	attempt, exists := t.attempts[identifier]
	if !exists {
		return false, 0
	}

	if attempt.lockedUntil.IsZero() {
		return false, 0
	}

	remaining := time.Until(attempt.lockedUntil)
	if remaining <= 0 {
		return false, 0
	}

	return true, remaining
}

// ═══════════════════════════════════════════════════════════
// 🔍 INPUT VALIDATION & SANITIZATION
// ═══════════════════════════════════════════════════════════

// SanitizeEmail limpa e valida email
func SanitizeEmail(email string) (string, error) {
	email = strings.TrimSpace(strings.ToLower(email))
	
	emailRegex := regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)
	if !emailRegex.MatchString(email) {
		return "", fmt.Errorf("email inválido")
	}
	
	return email, nil
}

// SanitizeHostname limpa hostname
func SanitizeHostname(hostname string) (string, error) {
	hostname = strings.TrimSpace(hostname)
	
	// Permitir apenas alfanuméricos, hífens e pontos
	hostnameRegex := regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9.-]{0,253}[a-zA-Z0-9]$`)
	if !hostnameRegex.MatchString(hostname) && len(hostname) > 1 {
		return "", fmt.Errorf("hostname inválido")
	}
	
	return hostname, nil
}

// SanitizeSQL previne SQL injection (para queries dinâmicas)
func SanitizeSQL(input string) string {
	// Remover caracteres perigosos
	dangerous := []string{"'", "\"", ";", "--", "/*", "*/", "xp_", "sp_"}
	result := input
	for _, d := range dangerous {
		result = strings.ReplaceAll(result, d, "")
	}
	return result
}

// ValidateIPAddress valida endereço IP
func ValidateIPAddress(ip string) bool {
	return net.ParseIP(ip) != nil
}

// ═══════════════════════════════════════════════════════════
// 🛡️ SECURITY HEADERS MIDDLEWARE
// ═══════════════════════════════════════════════════════════

// SecurityHeaders adiciona headers de segurança
func SecurityHeaders() fiber.Handler {
	return func(c *fiber.Ctx) error {
		// Prevenir clickjacking
		c.Set("X-Frame-Options", "DENY")
		
		// Prevenir MIME type sniffing
		c.Set("X-Content-Type-Options", "nosniff")
		
		// XSS Protection
		c.Set("X-XSS-Protection", "1; mode=block")
		
		// Content Security Policy
		c.Set("Content-Security-Policy", "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' wss: https:")
		
		// HSTS (apenas se HTTPS)
		if c.Protocol() == "https" {
			c.Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains; preload")
		}
		
		// Referrer Policy
		c.Set("Referrer-Policy", "strict-origin-when-cross-origin")
		
		// Permissions Policy
		c.Set("Permissions-Policy", "geolocation=(), microphone=(), camera=()")
		
		// Remove Server header
		c.Set("Server", "")
		
		return c.Next()
	}
}

// ═══════════════════════════════════════════════════════════
// 📝 AUDIT LOGGING
// ═══════════════════════════════════════════════════════════

// AuditEvent representa um evento de auditoria
type AuditEvent struct {
	Timestamp   time.Time         `json:"timestamp"`
	EventType   string            `json:"event_type"`
	UserID      string            `json:"user_id,omitempty"`
	OrgID       string            `json:"org_id,omitempty"`
	IP          string            `json:"ip"`
	UserAgent   string            `json:"user_agent"`
	Resource    string            `json:"resource"`
	Action      string            `json:"action"`
	Status      string            `json:"status"`
	Details     map[string]string `json:"details,omitempty"`
}

// AuditEventType tipos de eventos
const (
	AuditLogin         = "auth.login"
	AuditLogout        = "auth.logout"
	AuditLoginFailed   = "auth.login_failed"
	AuditAPIKeyCreated = "apikey.created"
	AuditAPIKeyRevoked = "apikey.revoked"
	AuditUserCreated   = "user.created"
	AuditUserDeleted   = "user.deleted"
	AuditConfigChanged = "config.changed"
	AuditAlertCreated  = "alert.created"
	AuditDataExport    = "data.export"
)

// AuditLogger interface para logging de auditoria
type AuditLogger interface {
	Log(event AuditEvent) error
}

// ═══════════════════════════════════════════════════════════
// 🔐 SECRETS MANAGEMENT
// ═══════════════════════════════════════════════════════════

// GenerateSecureToken gera token seguro
func GenerateSecureToken(length int) string {
	bytes := make([]byte, length)
	if _, err := rand.Read(bytes); err != nil {
		panic("failed to generate secure token")
	}
	return base64.URLEncoding.EncodeToString(bytes)[:length]
}

// MaskSecret mascara segredos para logging
func MaskSecret(secret string) string {
	if len(secret) <= 8 {
		return "****"
	}
	return secret[:4] + "****" + secret[len(secret)-4:]
}

// ═══════════════════════════════════════════════════════════
// 🌐 IP FILTERING
// ═══════════════════════════════════════════════════════════

// IPWhitelist gerencia whitelist de IPs
type IPWhitelist struct {
	sync.RWMutex
	allowed map[string]bool
	cidrs   []*net.IPNet
}

// NewIPWhitelist cria whitelist
func NewIPWhitelist(ips []string, cidrs []string) *IPWhitelist {
	wl := &IPWhitelist{
		allowed: make(map[string]bool),
	}
	
	for _, ip := range ips {
		wl.allowed[ip] = true
	}
	
	for _, cidr := range cidrs {
		_, network, err := net.ParseCIDR(cidr)
		if err == nil {
			wl.cidrs = append(wl.cidrs, network)
		}
	}
	
	return wl
}

// IsAllowed verifica se IP está permitido
func (wl *IPWhitelist) IsAllowed(ip string) bool {
	wl.RLock()
	defer wl.RUnlock()
	
	// Verificar lista direta
	if wl.allowed[ip] {
		return true
	}
	
	// Verificar CIDRs
	parsedIP := net.ParseIP(ip)
	if parsedIP == nil {
		return false
	}
	
	for _, network := range wl.cidrs {
		if network.Contains(parsedIP) {
			return true
		}
	}
	
	return false
}
