package ipcrypt

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"encoding/base64"
	"encoding/hex"
	"fmt"
	"net"
	"time"

	"go.uber.org/zap"

	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/validation"
)

// IPEncryptor provides secure IP address encryption for GDPR compliance
// Uses AES-GCM (Galois/Counter Mode) for authenticated encryption
// Encrypts IP addresses before storage and provides expiration checking
type IPEncryptor struct {
	gcm    cipher.AEAD
	logger *zap.Logger
}

// NewIPEncryptor creates a new IP encryptor with the given encryption key
// keyHex should be a 32-character hex string (16 bytes for AES-128)
// or 64-character hex string (32 bytes for AES-256)
// Example: "0123456789abcdef0123456789abcdef" (AES-128)
// Recommended: Use AES-256 with 64-character hex key
func NewIPEncryptor(keyHex string, logger *zap.Logger) (*IPEncryptor, error) {
	// Decode hex key to bytes
	keyBytes, err := hex.DecodeString(keyHex)
	if err != nil {
		return nil, fmt.Errorf("invalid hex key: %w", err)
	}

	// AES requires exactly 16, 24, or 32 bytes
	if len(keyBytes) != 16 && len(keyBytes) != 24 && len(keyBytes) != 32 {
		return nil, fmt.Errorf("key must be 16, 24, or 32 bytes (32, 48, or 64 hex characters), got %d bytes", len(keyBytes))
	}

	// Create AES cipher block
	block, err := aes.NewCipher(keyBytes)
	if err != nil {
		return nil, fmt.Errorf("failed to create cipher: %w", err)
	}

	// Create GCM (Galois/Counter Mode) for authenticated encryption
	// GCM provides both confidentiality and integrity
	gcm, err := cipher.NewGCM(block)
	if err != nil {
		return nil, fmt.Errorf("failed to create GCM: %w", err)
	}

	logger.Info("IP encryptor initialized with AES-GCM",
		zap.Int("key_length_bytes", len(keyBytes)),
		zap.Int("nonce_size", gcm.NonceSize()),
		zap.Int("overhead", gcm.Overhead()))

	return &IPEncryptor{
		gcm:    gcm,
		logger: logger.Named("ip-encryptor"),
	}, nil
}

// Encrypt encrypts an IP address for secure storage using AES-GCM
// Returns base64-encoded encrypted IP address with embedded nonce
// Format: base64(nonce + ciphertext + auth_tag)
// Supports both IPv4 and IPv6 addresses
//
// Security Properties:
// - Semantic security: same IP address produces different ciphertext each time
// - Authentication: tampering with ciphertext is detected
// - Unique nonce per encryption prevents pattern analysis
func (e *IPEncryptor) Encrypt(ipAddress string) (string, error) {
	if ipAddress == "" {
		return "", nil // Empty string remains empty
	}

	// Parse IP address to validate format
	ip := net.ParseIP(ipAddress)
	if ip == nil {
		e.logger.Warn("invalid IP address format",
			zap.String("ip", validation.MaskIP(ipAddress)))
		return "", fmt.Errorf("invalid IP address: %s", validation.MaskIP(ipAddress))
	}

	// Convert to 16-byte representation (IPv4 gets converted to IPv6 format)
	ipBytes := ip.To16()
	if ipBytes == nil {
		return "", fmt.Errorf("failed to convert IP to 16-byte format")
	}

	// Generate a random nonce (number used once)
	// GCM requires a unique nonce for each encryption operation
	nonce := make([]byte, e.gcm.NonceSize())
	if _, err := rand.Read(nonce); err != nil {
		e.logger.Error("failed to generate nonce", zap.Error(err))
		return "", fmt.Errorf("failed to generate nonce: %w", err)
	}

	// Encrypt the IP bytes using AES-GCM
	// GCM appends the authentication tag to the ciphertext
	// nil additional data means no associated data
	ciphertext := e.gcm.Seal(nil, nonce, ipBytes, nil)

	// Prepend nonce to ciphertext for storage
	// Format: nonce || ciphertext+tag
	encryptedData := append(nonce, ciphertext...)

	// Encode to base64 for database storage (text-safe)
	encryptedBase64 := base64.StdEncoding.EncodeToString(encryptedData)

	e.logger.Debug("IP address encrypted with AES-GCM",
		zap.Int("plaintext_length", len(ipBytes)),
		zap.Int("nonce_length", len(nonce)),
		zap.Int("ciphertext_length", len(ciphertext)),
		zap.Int("total_encrypted_length", len(encryptedData)),
		zap.Int("base64_length", len(encryptedBase64)))

	return encryptedBase64, nil
}

// Decrypt decrypts an encrypted IP address
// Takes base64-encoded encrypted IP and returns original IP address string
// Verifies authentication tag to detect tampering
func (e *IPEncryptor) Decrypt(encryptedBase64 string) (string, error) {
	if encryptedBase64 == "" {
		return "", nil // Empty string remains empty
	}

	// Decode base64 to bytes
	encryptedData, err := base64.StdEncoding.DecodeString(encryptedBase64)
	if err != nil {
		e.logger.Warn("invalid base64-encoded encrypted IP",
			zap.String("base64", encryptedBase64),
			zap.Error(err))
		return "", fmt.Errorf("invalid base64 encoding: %w", err)
	}

	// Extract nonce from the beginning
	nonceSize := e.gcm.NonceSize()
	if len(encryptedData) < nonceSize {
		return "", fmt.Errorf("encrypted data too short: expected at least %d bytes, got %d", nonceSize, len(encryptedData))
	}

	nonce := encryptedData[:nonceSize]
	ciphertext := encryptedData[nonceSize:]

	// Decrypt and verify authentication tag using AES-GCM
	ipBytes, err := e.gcm.Open(nil, nonce, ciphertext, nil)
	if err != nil {
		e.logger.Warn("failed to decrypt IP address (authentication failed or corrupted data)",
			zap.Error(err))
		return "", fmt.Errorf("decryption failed: %w", err)
	}

	// Convert bytes to IP address
	ip := net.IP(ipBytes)
	if ip == nil {
		return "", fmt.Errorf("failed to parse decrypted IP bytes")
	}

	// Convert to string
	ipString := ip.String()

	e.logger.Debug("IP address decrypted with AES-GCM",
		zap.Int("encrypted_length", len(encryptedData)),
		zap.Int("decrypted_length", len(ipBytes)))

	return ipString, nil
}

// IsExpired checks if an IP address timestamp has expired (> 90 days old)
// GDPR compliance: IP addresses must be deleted after 90 days
func (e *IPEncryptor) IsExpired(timestamp time.Time) bool {
	if timestamp.IsZero() {
		return false // No timestamp means not expired (will be cleaned up later)
	}

	// Calculate age in days
	age := time.Since(timestamp)
	ageInDays := int(age.Hours() / 24)

	expired := ageInDays > 90

	if expired {
		e.logger.Debug("IP timestamp expired",
			zap.Time("timestamp", timestamp),
			zap.Int("age_days", ageInDays))
	}

	return expired
}

// ShouldCleanup checks if an IP address should be cleaned up based on timestamp
// Returns true if timestamp is older than 90 days OR if timestamp is zero (unset)
func (e *IPEncryptor) ShouldCleanup(timestamp time.Time) bool {
	// Always cleanup if timestamp is not set (backwards compatibility)
	if timestamp.IsZero() {
		return false // Don't cleanup unset timestamps immediately
	}

	return e.IsExpired(timestamp)
}

// ValidateKey validates that a key is properly formatted for IP encryption
// Returns true if key is valid 32-character hex string (AES-128) or 64-character (AES-256)
func ValidateKey(keyHex string) error {
	// Check length (must be 16, 24, or 32 bytes = 32, 48, or 64 hex chars)
	if len(keyHex) != 32 && len(keyHex) != 48 && len(keyHex) != 64 {
		return fmt.Errorf("key must be 32, 48, or 64 hex characters, got %d characters", len(keyHex))
	}

	// Check if valid hex
	_, err := hex.DecodeString(keyHex)
	if err != nil {
		return fmt.Errorf("key must be valid hex string: %w", err)
	}

	return nil
}
