package migration

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"time"

	"github.com/Fimeg/RedFlag/aggregator-agent/internal/common"
)

// DockerDetection represents Docker secrets detection results
type DockerDetection struct {
	DockerAvailable     bool     `json:"docker_available"`
	SecretsMountPath    string   `json:"secrets_mount_path"`
	RequiredSecrets     []string `json:"required_secrets"`
	ExistingSecrets     []string `json:"existing_secrets"`
	MigrateToSecrets    bool     `json:"migrate_to_secrets"`
	SecretFiles         []common.AgentFile `json:"secret_files"`
	DetectionTime       time.Time `json:"detection_time"`
}

// SecretFile represents a file that should be migrated to Docker secrets
type SecretFile struct {
	Name        string `json:"name"`
	SourcePath  string `json:"source_path"`
	SecretPath  string `json:"secret_path"`
	Encrypted   bool   `json:"encrypted"`
	Checksum    string `json:"checksum"`
	Size        int64  `json:"size"`
}

// DockerConfig holds Docker secrets configuration
type DockerConfig struct {
	Enabled       bool     `json:"enabled"`
	SecretsPath   string   `json:"secrets_path"`
	EncryptionKey string   `json:"encryption_key,omitempty"`
	Secrets       map[string]string `json:"secrets,omitempty"`
}

// GetDockerSecretsPath returns the platform-specific Docker secrets path
func GetDockerSecretsPath() string {
	if runtime.GOOS == "windows" {
		return `C:\ProgramData\Docker\secrets`
	}
	return "/run/secrets"
}

// DetectDockerSecretsRequirements detects if Docker secrets migration is needed
func DetectDockerSecretsRequirements(config *FileDetectionConfig) (*DockerDetection, error) {
	detection := &DockerDetection{
		DetectionTime:    time.Now(),
		SecretsMountPath: GetDockerSecretsPath(),
	}

	// Check if Docker secrets directory exists
	if _, err := os.Stat(detection.SecretsMountPath); err == nil {
		detection.DockerAvailable = true
		fmt.Printf("[DOCKER] Docker secrets mount path detected: %s\n", detection.SecretsMountPath)
	} else {
		fmt.Printf("[DOCKER] Docker secrets not available: %s\n", err)
		return detection, nil
	}

	// Scan for sensitive files that should be migrated to secrets
	secretFiles, err := scanSecretFiles(config)
	if err != nil {
		return nil, fmt.Errorf("failed to scan for secret files: %w", err)
	}

	detection.SecretFiles = secretFiles
	detection.MigrateToSecrets = len(secretFiles) > 0

	// Identify required secrets
	detection.RequiredSecrets = identifyRequiredSecrets(secretFiles)

	// Check existing secrets
	detection.ExistingSecrets = scanExistingSecrets(detection.SecretsMountPath)

	return detection, nil
}

// scanSecretFiles scans for files containing sensitive data
func scanSecretFiles(config *FileDetectionConfig) ([]common.AgentFile, error) {
	var secretFiles []common.AgentFile

	// Define sensitive file patterns
	secretPatterns := []string{
		"agent.key",
		"server.key",
		"ca.crt",
		"*.pem",
		"*.key",
		"config.json", // Will be filtered for sensitive content
	}

	// Scan new directory paths for secret files
	for _, dirPath := range []string{config.NewConfigPath, config.NewStatePath} {
		if _, err := os.Stat(dirPath); err == nil {
			files, err := scanSecretDirectory(dirPath, secretPatterns)
			if err != nil {
				return nil, fmt.Errorf("failed to scan directory %s for secrets: %w", dirPath, err)
			}
			secretFiles = append(secretFiles, files...)
		}
	}

	return secretFiles, nil
}

// scanSecretDirectory scans a directory for files that may contain secrets
func scanSecretDirectory(dirPath string, patterns []string) ([]common.AgentFile, error) {
	var files []common.AgentFile

	err := filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		if info.IsDir() {
			return nil
		}

		// Check if file matches secret patterns
		if !matchesSecretPattern(path, patterns) {
			// For config.json, check if it contains sensitive data
			if filepath.Base(path) == "config.json" {
				if hasSensitiveContent(path) {
					return addSecretFile(&files, path, info)
				}
			}
			return nil
		}

		return addSecretFile(&files, path, info)
	})

	return files, err
}

// addSecretFile adds a file to the secret files list
func addSecretFile(files *[]common.AgentFile, path string, info os.FileInfo) error {
	checksum, err := calculateFileChecksum(path)
	if err != nil {
		return nil // Skip files we can't read
	}

	file := common.AgentFile{
		Path:         path,
		Size:         info.Size(),
		ModifiedTime: info.ModTime(),
		Checksum:     checksum,
		Required:     true,
		Migrate:      true,
		Description:  getSecretFileDescription(path),
	}

	*files = append(*files, file)
	return nil
}

// matchesSecretPattern checks if a file path matches secret patterns
func matchesSecretPattern(path string, patterns []string) bool {
	base := filepath.Base(path)
	for _, pattern := range patterns {
		if matched, _ := filepath.Match(pattern, base); matched {
			return true
		}
	}
	return false
}

// hasSensitiveContent checks if a config file contains sensitive data
func hasSensitiveContent(configPath string) bool {
	data, err := os.ReadFile(configPath)
	if err != nil {
		return false
	}

	var config map[string]interface{}
	if err := json.Unmarshal(data, &config); err != nil {
		return false
	}

	// Check for sensitive fields
	sensitiveFields := []string{
		"password", "token", "key", "secret", "credential",
		"proxy", "tls", "certificate", "private",
	}

	for _, field := range sensitiveFields {
		if containsSensitiveField(config, field) {
			return true
		}
	}

	return false
}

// containsSensitiveField recursively checks for sensitive fields in config
func containsSensitiveField(config map[string]interface{}, field string) bool {
	for key, value := range config {
		if containsString(key, field) {
			return true
		}

		if nested, ok := value.(map[string]interface{}); ok {
			if containsSensitiveField(nested, field) {
				return true
			}
		}
	}
	return false
}

// containsString checks if a string contains a substring (case-insensitive)
func containsString(s, substr string) bool {
	s = strings.ToLower(s)
	substr = strings.ToLower(substr)
	return strings.Contains(s, substr)
}

// identifyRequiredSecrets identifies which secrets need to be created
func identifyRequiredSecrets(secretFiles []common.AgentFile) []string {
	var secrets []string
	for _, file := range secretFiles {
		secretName := filepath.Base(file.Path)
		if file.Path == "config.json" {
			secrets = append(secrets, "config.json.enc")
		} else {
			secrets = append(secrets, secretName)
		}
	}
	return secrets
}

// scanExistingSecrets scans the Docker secrets directory for existing secrets
func scanExistingSecrets(secretsPath string) []string {
	var secrets []string

	entries, err := os.ReadDir(secretsPath)
	if err != nil {
		return secrets
	}

	for _, entry := range entries {
		if !entry.IsDir() {
			secrets = append(secrets, entry.Name())
		}
	}

	return secrets
}

// getSecretFileDescription returns a description for a secret file
func getSecretFileDescription(path string) string {
	base := filepath.Base(path)
	switch {
	case base == "agent.key":
		return "Agent private key"
	case base == "server.key":
		return "Server private key"
	case base == "ca.crt":
		return "Certificate authority certificate"
	case strings.Contains(base, ".key"):
		return "Private key file"
	case strings.Contains(base, ".crt") || strings.Contains(base, ".pem"):
		return "Certificate file"
	case base == "config.json":
		return "Configuration file with sensitive data"
	default:
		return "Secret file"
	}
}

// EncryptFile encrypts a file using AES-256-GCM
func EncryptFile(inputPath, outputPath, key string) error {
	// Generate key from passphrase
	keyBytes := sha256.Sum256([]byte(key))

	// Read input file
	plaintext, err := os.ReadFile(inputPath)
	if err != nil {
		return fmt.Errorf("failed to read input file: %w", err)
	}

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

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

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

	// Encrypt
	ciphertext := gcm.Seal(nonce, nonce, plaintext, nil)

	// Write encrypted file
	if err := os.WriteFile(outputPath, ciphertext, 0600); err != nil {
		return fmt.Errorf("failed to write encrypted file: %w", err)
	}

	return nil
}

// DecryptFile decrypts a file using AES-256-GCM
func DecryptFile(inputPath, outputPath, key string) error {
	// Generate key from passphrase
	keyBytes := sha256.Sum256([]byte(key))

	// Read encrypted file
	ciphertext, err := os.ReadFile(inputPath)
	if err != nil {
		return fmt.Errorf("failed to read encrypted file: %w", err)
	}

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

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

	// Check minimum length
	if len(ciphertext) < gcm.NonceSize() {
		return fmt.Errorf("ciphertext too short")
	}

	// Extract nonce and ciphertext
	nonce := ciphertext[:gcm.NonceSize()]
	ciphertext = ciphertext[gcm.NonceSize():]

	// Decrypt
	plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
	if err != nil {
		return fmt.Errorf("failed to decrypt: %w", err)
	}

	// Write decrypted file
	if err := os.WriteFile(outputPath, plaintext, 0600); err != nil {
		return fmt.Errorf("failed to write decrypted file: %w", err)
	}

	return nil
}

// GenerateEncryptionKey generates a random encryption key
func GenerateEncryptionKey() (string, error) {
	bytes := make([]byte, 32)
	if _, err := rand.Read(bytes); err != nil {
		return "", fmt.Errorf("failed to generate encryption key: %w", err)
	}
	return hex.EncodeToString(bytes), nil
}

// IsDockerEnvironment checks if running in Docker environment
func IsDockerEnvironment() bool {
	// Check for .dockerenv file
	if _, err := os.Stat("/.dockerenv"); err == nil {
		return true
	}

	// Check for Docker in cgroup
	if data, err := os.ReadFile("/proc/1/cgroup"); err == nil {
		if containsString(string(data), "docker") {
			return true
		}
	}

	return false
}