package security

import (
	"containr/internal/database"
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"crypto/sha256"
	"database/sql"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"net"
	"strings"
	"time"

	"github.com/google/uuid"
)

// EncryptionManager handles data encryption and decryption
type EncryptionManager struct {
	gcm cipher.AEAD
}

// NewEncryptionManager creates a new encryption manager
func NewEncryptionManager(key string) (*EncryptionManager, error) {
	// Convert key to 32 bytes for AES-256
	keyHash := sha256.Sum256([]byte(key))

	block, err := aes.NewCipher(keyHash[:])
	if err != nil {
		return nil, fmt.Errorf("failed to create cipher: %w", err)
	}

	gcm, err := cipher.NewGCM(block)
	if err != nil {
		return nil, fmt.Errorf("failed to create GCM: %w", err)
	}

	return &EncryptionManager{gcm: gcm}, nil
}

// Encrypt encrypts data using AES-256 GCM
func (em *EncryptionManager) Encrypt(plaintext string) (string, error) {
	if plaintext == "" {
		return "", nil
	}

	nonce := make([]byte, em.gcm.NonceSize())
	if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
		return "", fmt.Errorf("failed to generate nonce: %w", err)
	}

	ciphertext := em.gcm.Seal(nonce, nonce, []byte(plaintext), nil)
	return base64.StdEncoding.EncodeToString(ciphertext), nil
}

// Decrypt decrypts data using AES-256 GCM
func (em *EncryptionManager) Decrypt(ciphertext string) (string, error) {
	if ciphertext == "" {
		return "", nil
	}

	data, err := base64.StdEncoding.DecodeString(ciphertext)
	if err != nil {
		return "", fmt.Errorf("failed to decode base64: %w", err)
	}

	nonceSize := em.gcm.NonceSize()
	if len(data) < nonceSize {
		return "", fmt.Errorf("ciphertext too short")
	}

	nonce, ciphertext_bytes := data[:nonceSize], data[nonceSize:]
	plaintext, err := em.gcm.Open(nil, nonce, ciphertext_bytes, nil)
	if err != nil {
		return "", fmt.Errorf("failed to decrypt: %w", err)
	}

	return string(plaintext), nil
}

// EncryptSensitiveData encrypts sensitive data fields
func (em *EncryptionManager) EncryptSensitiveData(data map[string]interface{}) (map[string]interface{}, error) {
	result := make(map[string]interface{})

	for key, value := range data {
		if em.isSensitiveField(key) {
			strValue, ok := value.(string)
			if ok {
				encrypted, err := em.Encrypt(strValue)
				if err != nil {
					return nil, fmt.Errorf("failed to encrypt field %s: %w", key, err)
				}
				result[key] = encrypted
			} else {
				result[key] = value
			}
		} else {
			result[key] = value
		}
	}

	return result, nil
}

// DecryptSensitiveData decrypts sensitive data fields
func (em *EncryptionManager) DecryptSensitiveData(data map[string]interface{}) (map[string]interface{}, error) {
	result := make(map[string]interface{})

	for key, value := range data {
		if em.isSensitiveField(key) {
			strValue, ok := value.(string)
			if ok {
				decrypted, err := em.Decrypt(strValue)
				if err != nil {
					return nil, fmt.Errorf("failed to decrypt field %s: %w", key, err)
				}
				result[key] = decrypted
			} else {
				result[key] = value
			}
		} else {
			result[key] = value
		}
	}

	return result, nil
}

// isSensitiveField determines if a field contains sensitive data
func (em *EncryptionManager) isSensitiveField(fieldName string) bool {
	sensitiveFields := []string{
		"password", "secret", "token", "key", "api_key", "private_key",
		"database_url", "connection_string", "credit_card", "ssn",
		"social_security", "bank_account", "auth_token", "jwt_secret",
		"encryption_key", "webhook_secret", "oauth_secret", "access_token",
		"refresh_token", "client_secret", "private", "confidential",
	}

	fieldName = strings.ToLower(fieldName)
	for _, sensitive := range sensitiveFields {
		if strings.Contains(fieldName, sensitive) {
			return true
		}
	}
	return false
}

// DataRetentionManager handles data retention policies
type DataRetentionManager struct {
	encryptionManager *EncryptionManager
}

// NewDataRetentionManager creates a new data retention manager
func NewDataRetentionManager(encryptionManager *EncryptionManager) *DataRetentionManager {
	return &DataRetentionManager{
		encryptionManager: encryptionManager,
	}
}

// RetentionPolicy defines data retention rules
type RetentionPolicy struct {
	ID              string        `json:"id"`
	Name            string        `json:"name"`
	DataType        string        `json:"data_type"`
	RetentionPeriod time.Duration `json:"retention_period"`
	Action          string        `json:"action"` // "delete", "anonymize", "archive"
	Enabled         bool          `json:"enabled"`
	CreatedAt       time.Time     `json:"created_at"`
}

// AnonymizedData represents anonymized user data
type AnonymizedData struct {
	OriginalID   string    `json:"original_id"`
	AnonymizedID string    `json:"anonymized_id"`
	DataType     string    `json:"data_type"`
	AnonymizedAt time.Time `json:"anonymized_at"`
	RetainedData string    `json:"retained_data"` // Encrypted non-sensitive data
}

// AnonymizeUserData anonymizes user data for GDPR compliance
func (drm *DataRetentionManager) AnonymizeUserData(userData map[string]interface{}) (*AnonymizedData, error) {
	anonymizedID := fmt.Sprintf("anon_%d", time.Now().UnixNano())

	// Separate sensitive and non-sensitive data
	sensitiveData := make(map[string]interface{})
	nonSensitiveData := make(map[string]interface{})

	for key, value := range userData {
		if drm.isPersonalData(key) {
			sensitiveData[key] = value
		} else {
			nonSensitiveData[key] = value
		}
	}

	// Encrypt non-sensitive data for retention
	nonSensitiveJSON, _ := json.Marshal(nonSensitiveData)
	encryptedRetainedData, err := drm.encryptionManager.Encrypt(string(nonSensitiveJSON))
	if err != nil {
		return nil, fmt.Errorf("failed to encrypt retained data: %w", err)
	}

	// Create anonymized record
	anonymized := &AnonymizedData{
		OriginalID:   fmt.Sprintf("%v", userData["id"]),
		AnonymizedID: anonymizedID,
		DataType:     "user",
		AnonymizedAt: time.Now(),
		RetainedData: encryptedRetainedData,
	}

	return anonymized, nil
}

// isPersonalData determines if data is personal information under GDPR
func (drm *DataRetentionManager) isPersonalData(fieldName string) bool {
	personalDataFields := []string{
		"name", "email", "phone", "address", "birthdate", "gender",
		"ip_address", "user_agent", "location", "biometric", "health",
		"political", "religious", "sexual", "criminal", "financial",
		"education", "employment", "family", "social", "behavioral",
		"identifier", "cookie", "tracking", "profile", "preferences",
	}

	fieldName = strings.ToLower(fieldName)
	for _, personal := range personalDataFields {
		if strings.Contains(fieldName, personal) {
			return true
		}
	}
	return false
}

// ApplyRetentionPolicy applies retention policies to data
func (drm *DataRetentionManager) ApplyRetentionPolicy(dataType string, dataTimestamp time.Time, policy RetentionPolicy) string {
	if !policy.Enabled {
		return "retain"
	}

	expiryDate := dataTimestamp.Add(policy.RetentionPeriod)
	if time.Now().Before(expiryDate) {
		return "retain"
	}

	return policy.Action
}

// GenerateDataSubjectReport generates a report of all data held about a user
func (drm *DataRetentionManager) GenerateDataSubjectReport(userID string, userData map[string]interface{}) (map[string]interface{}, error) {
	report := map[string]interface{}{
		"user_id":            userID,
		"report_generated":   time.Now(),
		"data_categories":    drm.categorizeUserData(userData),
		"retention_policies": drm.getApplicablePolicies(userData),
		"data_sources":       []string{"database", "logs", "analytics"},
	}

	return report, nil
}

// categorizeUserData categorizes user data by type
func (drm *DataRetentionManager) categorizeUserData(userData map[string]interface{}) map[string][]string {
	categories := map[string][]string{
		"identity":    {},
		"contact":     {},
		"technical":   {},
		"behavioral":  {},
		"preferences": {},
	}

	for key := range userData {
		lowerKey := strings.ToLower(key)

		switch {
		case strings.Contains(lowerKey, "name") || strings.Contains(lowerKey, "id"):
			categories["identity"] = append(categories["identity"], key)
		case strings.Contains(lowerKey, "email") || strings.Contains(lowerKey, "phone"):
			categories["contact"] = append(categories["contact"], key)
		case strings.Contains(lowerKey, "ip") || strings.Contains(lowerKey, "agent"):
			categories["technical"] = append(categories["technical"], key)
		case strings.Contains(lowerKey, "activity") || strings.Contains(lowerKey, "behavior"):
			categories["behavioral"] = append(categories["behavioral"], key)
		case strings.Contains(lowerKey, "preference") || strings.Contains(lowerKey, "setting"):
			categories["preferences"] = append(categories["preferences"], key)
		}
	}

	return categories
}

// getApplicablePolicies returns applicable retention policies
func (drm *DataRetentionManager) getApplicablePolicies(userData map[string]interface{}) []string {
	policies := []string{
		"user_data_2_years",
		"analytics_data_6_months",
		"logs_data_90_days",
		"deleted_users_30_days",
	}

	return policies
}

// AuditLogger handles security audit logging
type AuditLogger struct {
	encryptionManager *EncryptionManager
	db                *database.DB
}

// NewAuditLogger creates a new audit logger
func NewAuditLogger(encryptionManager *EncryptionManager, db *database.DB) *AuditLogger {
	return &AuditLogger{
		encryptionManager: encryptionManager,
		db:                db,
	}
}

// AuditEvent represents a security audit event
type AuditEvent struct {
	ID         string                 `json:"id"`
	Timestamp  time.Time              `json:"timestamp"`
	UserID     string                 `json:"user_id,omitempty"`
	ResourceID string                 `json:"resource_id,omitempty"`
	Action     string                 `json:"action"`
	Resource   string                 `json:"resource"`
	Details    map[string]interface{} `json:"details"`
	IPAddress  string                 `json:"ip_address"`
	UserAgent  string                 `json:"user_agent"`
	Success    bool                   `json:"success"`
}

// LogAuditEvent logs a security audit event
func (al *AuditLogger) LogAuditEvent(event AuditEvent) error {
	if al == nil || al.db == nil {
		return nil
	}

	if strings.TrimSpace(event.ID) == "" {
		event.ID = uuid.New().String()
	}
	if event.Timestamp.IsZero() {
		event.Timestamp = time.Now().UTC()
	}

	// Encrypt sensitive details
	if event.Details != nil {
		encryptedDetails, err := al.encryptionManager.EncryptSensitiveData(event.Details)
		if err != nil {
			return fmt.Errorf("failed to encrypt audit details: %w", err)
		}
		event.Details = encryptedDetails
	}

	detailsJSON, err := json.Marshal(event.Details)
	if err != nil {
		return fmt.Errorf("failed to marshal audit details: %w", err)
	}

	var userID sql.NullString
	if id := strings.TrimSpace(event.UserID); id != "" {
		if _, err := uuid.Parse(id); err == nil {
			userID = sql.NullString{String: id, Valid: true}
		}
	}

	var resourceID sql.NullString
	if id := strings.TrimSpace(event.ResourceID); id != "" {
		if _, err := uuid.Parse(id); err == nil {
			resourceID = sql.NullString{String: id, Valid: true}
		}
	}

	var ipAddress sql.NullString
	if ip := strings.TrimSpace(event.IPAddress); ip != "" && net.ParseIP(ip) != nil {
		ipAddress = sql.NullString{String: ip, Valid: true}
	}

	var userAgent sql.NullString
	if ua := strings.TrimSpace(event.UserAgent); ua != "" {
		userAgent = sql.NullString{String: ua, Valid: true}
	}

	_, err = al.db.Exec(
		`INSERT INTO audit_logs (
			id, user_id, resource, resource_id, action, details, ip_address, user_agent, created_at
		) VALUES ($1, $2, $3, $4, $5, $6::jsonb, $7, $8, $9)`,
		event.ID,
		userID,
		event.Resource,
		resourceID,
		event.Action,
		string(detailsJSON),
		ipAddress,
		userAgent,
		event.Timestamp,
	)
	if err != nil {
		return fmt.Errorf("failed to persist audit event: %w", err)
	}

	return nil
}

// LogSecurityEvent logs security-related events
func (al *AuditLogger) LogSecurityEvent(userID, resourceID, action, resource string, details map[string]interface{}, ipAddress, userAgent string, success bool) error {
	event := AuditEvent{
		UserID:     userID,
		ResourceID: resourceID,
		Action:     action,
		Resource:   resource,
		Details:    details,
		IPAddress:  ipAddress,
		UserAgent:  userAgent,
		Success:    success,
	}

	return al.LogAuditEvent(event)
}
