package migration

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/Fimeg/RedFlag/aggregator-agent/internal/common"
	"github.com/Fimeg/RedFlag/aggregator-agent/internal/event"
	"github.com/Fimeg/RedFlag/aggregator-agent/internal/models"
	"github.com/google/uuid"
)

// MigrationPlan represents a complete migration plan
type MigrationPlan struct {
	Detection            *MigrationDetection `json:"detection"`
	TargetVersion        string              `json:"target_version"`
	Config               *FileDetectionConfig `json:"config"`
	BackupPath           string              `json:"backup_path"`
	EstimatedDuration    time.Duration       `json:"estimated_duration"`
	RiskLevel            string              `json:"risk_level"` // low, medium, high
}

// MigrationResult represents the result of a migration execution
type MigrationResult struct {
	Success        bool          `json:"success"`
	StartTime      time.Time     `json:"start_time"`
	EndTime        time.Time     `json:"end_time"`
	Duration       time.Duration `json:"duration"`
	BackupPath     string        `json:"backup_path"`
	MigratedFiles  []string      `json:"migrated_files"`
	Errors         []string      `json:"errors"`
	Warnings       []string      `json:"warnings"`
	AppliedChanges []string      `json:"applied_changes"`
	RollbackAvailable bool       `json:"rollback_available"`
}

// MigrationExecutor handles the execution of migration plans
type MigrationExecutor struct {
	plan         *MigrationPlan
	result       *MigrationResult
	eventBuffer  *event.Buffer
	agentID      uuid.UUID
	stateManager *StateManager
}

// NewMigrationExecutor creates a new migration executor
func NewMigrationExecutor(plan *MigrationPlan, configPath string) *MigrationExecutor {
	return &MigrationExecutor{
		plan:         plan,
		result:       &MigrationResult{},
		stateManager: NewStateManager(configPath),
	}
}

// NewMigrationExecutorWithEvents creates a new migration executor with event buffering
func NewMigrationExecutorWithEvents(plan *MigrationPlan, eventBuffer *event.Buffer, agentID uuid.UUID, configPath string) *MigrationExecutor {
	return &MigrationExecutor{
		plan:         plan,
		result:       &MigrationResult{},
		eventBuffer:  eventBuffer,
		agentID:      agentID,
		stateManager: NewStateManager(configPath),
	}
}

// bufferEvent buffers a migration failure event
func (e *MigrationExecutor) bufferEvent(eventSubtype, severity, component, message string, metadata map[string]interface{}) {
	if e.eventBuffer == nil {
		return // Event buffering not enabled
	}

	// Use agent ID if available
	var agentIDPtr *uuid.UUID
	if e.agentID != uuid.Nil {
		agentIDPtr = &e.agentID
	}

	event := &models.SystemEvent{
		ID:           uuid.New(),
		AgentID:      agentIDPtr,
		EventType:    "migration_failure",
		EventSubtype: eventSubtype,
		Severity:     severity,
		Component:    component,
		Message:      message,
		Metadata:     metadata,
		CreatedAt:    time.Now(),
	}

	// Buffer the event (best effort)
	if err := e.eventBuffer.BufferEvent(event); err != nil {
		fmt.Printf("Warning: Failed to buffer migration event: %v\n", err)
	}
}

// ExecuteMigration executes the complete migration plan
func (e *MigrationExecutor) ExecuteMigration() (*MigrationResult, error) {
	e.result.StartTime = time.Now()
	e.result.BackupPath = e.plan.BackupPath

	fmt.Printf("[MIGRATION] Starting migration from %s to %s\n",
		e.plan.Detection.CurrentAgentVersion, e.plan.TargetVersion)

	// Phase 1: Create backups
	if err := e.createBackups(); err != nil {
		e.bufferEvent("backup_creation_failure", "error", "migration_executor",
			fmt.Sprintf("Backup creation failed: %v", err),
			map[string]interface{}{
				"error":      err.Error(),
				"backup_path": e.plan.BackupPath,
				"phase":      "backup_creation",
			})
		return e.completeMigration(false, fmt.Errorf("backup creation failed: %w", err))
	}
	e.result.AppliedChanges = append(e.result.AppliedChanges, "Created backups at "+e.plan.BackupPath)

	// Phase 2: Directory migration
	if contains(e.plan.Detection.RequiredMigrations, "directory_migration") {
		if err := e.migrateDirectories(); err != nil {
			e.bufferEvent("directory_migration_failure", "error", "migration_executor",
				fmt.Sprintf("Directory migration failed: %v", err),
				map[string]interface{}{
					"error": err.Error(),
					"phase": "directory_migration",
				})
			return e.completeMigration(false, fmt.Errorf("directory migration failed: %w", err))
		}
		e.result.AppliedChanges = append(e.result.AppliedChanges, "Migrated directories")

		// Mark directory migration as completed
		if err := e.stateManager.MarkMigrationCompleted("directory_migration", e.plan.BackupPath, e.plan.TargetVersion); err != nil {
			fmt.Printf("[MIGRATION] Warning: Failed to mark directory migration as completed: %v\n", err)
		}
	}

	// Phase 3: Configuration migration
	if contains(e.plan.Detection.RequiredMigrations, "config_migration") {
		if err := e.migrateConfiguration(); err != nil {
			e.bufferEvent("configuration_migration_failure", "error", "migration_executor",
				fmt.Sprintf("Configuration migration failed: %v", err),
				map[string]interface{}{
					"error": err.Error(),
					"phase": "configuration_migration",
				})
			return e.completeMigration(false, fmt.Errorf("configuration migration failed: %w", err))
		}
		e.result.AppliedChanges = append(e.result.AppliedChanges, "Migrated configuration")

		// Mark configuration migration as completed
		if err := e.stateManager.MarkMigrationCompleted("config_migration", e.plan.BackupPath, e.plan.TargetVersion); err != nil {
			fmt.Printf("[MIGRATION] Warning: Failed to mark configuration migration as completed: %v\n", err)
		}
	}

	// Phase 4: Docker secrets migration (if available)
	if contains(e.plan.Detection.RequiredMigrations, "docker_secrets_migration") {
		if e.plan.Detection.DockerDetection == nil {
			e.bufferEvent("docker_migration_failure", "error", "migration_executor",
				"Docker secrets migration requested but detection data missing",
				map[string]interface{}{
					"error": "missing detection data",
					"phase": "docker_secrets_migration",
				})
			return e.completeMigration(false, fmt.Errorf("docker secrets migration requested but detection data missing"))
		}

		dockerExecutor := NewDockerSecretsExecutor(e.plan.Detection.DockerDetection, e.plan.Config)
		if err := dockerExecutor.ExecuteDockerSecretsMigration(); err != nil {
			e.bufferEvent("docker_migration_failure", "error", "migration_executor",
				fmt.Sprintf("Docker secrets migration failed: %v", err),
				map[string]interface{}{
					"error": err.Error(),
					"phase": "docker_secrets_migration",
				})
			return e.completeMigration(false, fmt.Errorf("docker secrets migration failed: %w", err))
		}
		e.result.AppliedChanges = append(e.result.AppliedChanges, "Migrated to Docker secrets")

		// Mark docker secrets migration as completed
		if err := e.stateManager.MarkMigrationCompleted("docker_secrets_migration", e.plan.BackupPath, e.plan.TargetVersion); err != nil {
			fmt.Printf("[MIGRATION] Warning: Failed to mark docker secrets migration as completed: %v\n", err)
		}
	}

	// Phase 5: Security hardening
	if contains(e.plan.Detection.RequiredMigrations, "security_hardening") {
		if err := e.applySecurityHardening(); err != nil {
			e.result.Warnings = append(e.result.Warnings,
				fmt.Sprintf("Security hardening incomplete: %v", err))
		} else {
			e.result.AppliedChanges = append(e.result.AppliedChanges, "Applied security hardening")

			// Mark security hardening as completed
			if err := e.stateManager.MarkMigrationCompleted("security_hardening", e.plan.BackupPath, e.plan.TargetVersion); err != nil {
				fmt.Printf("[MIGRATION] Warning: Failed to mark security hardening as completed: %v\n", err)
			}
		}
	}

	// Phase 6: Validation
	if err := e.validateMigration(); err != nil {
		e.bufferEvent("migration_validation_failure", "error", "migration_executor",
			fmt.Sprintf("Migration validation failed: %v", err),
			map[string]interface{}{
				"error": err.Error(),
				"phase": "validation",
			})
		return e.completeMigration(false, fmt.Errorf("migration validation failed: %w", err))
	}

	return e.completeMigration(true, nil)
}

// createBackups creates backups of all existing files
func (e *MigrationExecutor) createBackups() error {
	backupPath := e.plan.BackupPath
	if backupPath == "" {
		timestamp := time.Now().Format("2006-01-02-150405")
		backupPath = fmt.Sprintf(e.plan.Config.BackupDirPattern, timestamp)
		e.plan.BackupPath = backupPath
	}

	fmt.Printf("[MIGRATION] Creating backup at: %s\n", backupPath)

	// Create backup directory
	if err := os.MkdirAll(backupPath, 0755); err != nil {
		return fmt.Errorf("failed to create backup directory: %w", err)
	}

	// Backup all files in inventory
	allFiles := e.collectAllFiles()
	for _, file := range allFiles {
		if err := e.backupFile(file, backupPath); err != nil {
			return fmt.Errorf("failed to backup file %s: %w", file.Path, err)
		}
		e.result.MigratedFiles = append(e.result.MigratedFiles, file.Path)
	}

	return nil
}

// migrateDirectories handles directory migration from old to new paths
func (e *MigrationExecutor) migrateDirectories() error {
	fmt.Printf("[MIGRATION] Migrating directories...\n")

	// Create new directories
	newDirectories := []string{e.plan.Config.NewConfigPath, e.plan.Config.NewStatePath}
	for _, newPath := range newDirectories {
		if err := os.MkdirAll(newPath, 0755); err != nil {
			return fmt.Errorf("failed to create directory %s: %w", newPath, err)
		}
		fmt.Printf("[MIGRATION] Created directory: %s\n", newPath)
	}

	// Migrate files from old to new directories
	for _, oldDir := range e.plan.Detection.Inventory.OldDirectoryPaths {
		newDir := e.getNewDirectoryPath(oldDir)
		if newDir == "" {
			continue
		}

		if err := e.migrateDirectoryContents(oldDir, newDir); err != nil {
			return fmt.Errorf("failed to migrate directory %s to %s: %w", oldDir, newDir, err)
		}
		fmt.Printf("[MIGRATION] Migrated: %s → %s\n", oldDir, newDir)
	}

	return nil
}

// migrateConfiguration handles configuration file migration
func (e *MigrationExecutor) migrateConfiguration() error {
	fmt.Printf("[MIGRATION] Migrating configuration...\n")

	// Find and migrate config files
	for _, configFile := range e.plan.Detection.Inventory.ConfigFiles {
		if strings.Contains(configFile.Path, "config.json") {
			newPath := e.getNewConfigPath(configFile.Path)
			if newPath != "" && newPath != configFile.Path {
				if err := e.migrateConfigFile(configFile.Path, newPath); err != nil {
					return fmt.Errorf("failed to migrate config file: %w", err)
				}
				fmt.Printf("[MIGRATION] Migrated config: %s → %s\n", configFile.Path, newPath)
			}
		}
	}

	return nil
}

// applySecurityHardening applies security-related configurations
func (e *MigrationExecutor) applySecurityHardening() error {
	fmt.Printf("[MIGRATION] Applying security hardening...\n")

	// This would integrate with the config system to apply security defaults
	// For now, just log what would be applied

	for _, feature := range e.plan.Detection.MissingSecurityFeatures {
		switch feature {
		case "nonce_validation":
			fmt.Printf("[MIGRATION] Enabling nonce validation\n")
		case "machine_id_binding":
			fmt.Printf("[MIGRATION] Configuring machine ID binding\n")
		case "ed25519_verification":
			fmt.Printf("[MIGRATION] Enabling Ed25519 verification\n")
		case "subsystem_configuration":
			fmt.Printf("[MIGRATION] Adding missing subsystem configurations\n")
		case "system_subsystem":
			fmt.Printf("[MIGRATION] Adding system scanner configuration\n")
		case "updates_subsystem":
			fmt.Printf("[MIGRATION] Adding updates subsystem configuration\n")
		}
	}

	return nil
}

// validateMigration validates that the migration was successful
func (e *MigrationExecutor) validateMigration() error {
	fmt.Printf("[MIGRATION] Validating migration...\n")

	// Check that new directories exist
	newDirectories := []string{e.plan.Config.NewConfigPath, e.plan.Config.NewStatePath}
	for _, newDir := range newDirectories {
		if _, err := os.Stat(newDir); err != nil {
			return fmt.Errorf("new directory %s not found: %w", newDir, err)
		}
	}

	// Check that config files exist in new location
	for _, configFile := range e.plan.Detection.Inventory.ConfigFiles {
		newPath := e.getNewConfigPath(configFile.Path)
		if newPath != "" {
			if _, err := os.Stat(newPath); err != nil {
				return fmt.Errorf("migrated config file %s not found: %w", newPath, err)
			}
		}
	}

	fmt.Printf("[INFO] [agent] [migration] validation_successful\n")
	return nil
}

// Helper methods

func (e *MigrationExecutor) collectAllFiles() []common.AgentFile {
	var allFiles []common.AgentFile
	allFiles = append(allFiles, e.plan.Detection.Inventory.ConfigFiles...)
	allFiles = append(allFiles, e.plan.Detection.Inventory.StateFiles...)
	allFiles = append(allFiles, e.plan.Detection.Inventory.BinaryFiles...)
	allFiles = append(allFiles, e.plan.Detection.Inventory.LogFiles...)
	allFiles = append(allFiles, e.plan.Detection.Inventory.CertificateFiles...)
	return allFiles
}

func (e *MigrationExecutor) backupFile(file common.AgentFile, backupPath string) error {
	// Check if file exists before attempting backup
	if _, err := os.Stat(file.Path); err != nil {
		if os.IsNotExist(err) {
			// File doesn't exist, log and skip
			fmt.Printf("[MIGRATION] [agent] [migration_executor] File does not exist, skipping backup: %s\n", file.Path)
			e.bufferEvent("backup_file_missing", "warning", "migration_executor",
				fmt.Sprintf("File does not exist, skipping backup: %s", file.Path),
				map[string]interface{}{
					"file_path": file.Path,
					"phase": "backup",
				})
			return nil
		}
		return fmt.Errorf("migration: failed to stat file %s: %w", file.Path, err)
	}

	// Clean paths to fix trailing slash issues
	cleanOldConfig := filepath.Clean(e.plan.Config.OldConfigPath)
	cleanOldState := filepath.Clean(e.plan.Config.OldStatePath)
	cleanPath := filepath.Clean(file.Path)
	var relPath string
	var err error

	// Try to get relative path based on expected file location
	// If file is under old config path, use that as base
	if strings.HasPrefix(cleanPath, cleanOldConfig) {
		relPath, err = filepath.Rel(cleanOldConfig, cleanPath)
		if err != nil || strings.Contains(relPath, "..") {
			// Fallback to filename if path traversal or error
			relPath = filepath.Base(cleanPath)
		}
	} else if strings.HasPrefix(cleanPath, cleanOldState) {
		relPath, err = filepath.Rel(cleanOldState, cleanPath)
		if err != nil || strings.Contains(relPath, "..") {
			// Fallback to filename if path traversal or error
			relPath = filepath.Base(cleanPath)
		}
	} else {
		// File is not in expected old locations - use just the filename
		// This happens for files already in the new location
		relPath = filepath.Base(cleanPath)
		// Add subdirectory based on file type to avoid collisions
		switch {
		case ContainsAny(cleanPath, []string{"config.json", "agent.key", "server.key", "ca.crt"}):
			relPath = filepath.Join("config", relPath)
		case ContainsAny(cleanPath, []string{
			"pending_acks.json", "public_key.cache", "last_scan.json", "metrics.json"}):
			relPath = filepath.Join("state", relPath)
		}
	}

	// Ensure backup path is clean
	cleanBackupPath := filepath.Clean(backupPath)
	backupFilePath := filepath.Join(cleanBackupPath, relPath)
	backupFilePath = filepath.Clean(backupFilePath)
	backupDir := filepath.Dir(backupFilePath)

	// Final safety check
	if strings.Contains(backupFilePath, "..") {
		return fmt.Errorf("migration: backup path contains parent directory reference: %s", backupFilePath)
	}

	if err := os.MkdirAll(backupDir, 0755); err != nil {
		return fmt.Errorf("migration: failed to create backup directory %s: %w", backupDir, err)
	}

	// Copy file to backup location
	if err := copyFile(cleanPath, backupFilePath); err != nil {
		return fmt.Errorf("migration: failed to copy file to backup: %w", err)
	}

	fmt.Printf("[MIGRATION] [agent] [migration_executor] Successfully backed up: %s\n", cleanPath)
	return nil
}

func (e *MigrationExecutor) migrateDirectoryContents(oldDir, newDir string) error {
	return filepath.Walk(oldDir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		if info.IsDir() {
			return nil
		}

		relPath, err := filepath.Rel(oldDir, path)
		if err != nil {
			return err
		}

		newPath := filepath.Join(newDir, relPath)
		newDirPath := filepath.Dir(newPath)

		if err := os.MkdirAll(newDirPath, 0755); err != nil {
			return fmt.Errorf("failed to create directory: %w", err)
		}

		if err := copyFile(path, newPath); err != nil {
			return fmt.Errorf("failed to copy file: %w", err)
		}

		return nil
	})
}

func (e *MigrationExecutor) migrateConfigFile(oldPath, newPath string) error {
	// This would use the config migration logic from the config package
	// For now, just copy the file
	return copyFile(oldPath, newPath)
}

func (e *MigrationExecutor) getNewDirectoryPath(oldPath string) string {
	if oldPath == e.plan.Config.OldConfigPath {
		return e.plan.Config.NewConfigPath
	}
	if oldPath == e.plan.Config.OldStatePath {
		return e.plan.Config.NewStatePath
	}
	return ""
}

func (e *MigrationExecutor) getNewConfigPath(oldPath string) string {
	if strings.HasPrefix(oldPath, e.plan.Config.OldConfigPath) {
		relPath := strings.TrimPrefix(oldPath, e.plan.Config.OldConfigPath)
		return filepath.Join(e.plan.Config.NewConfigPath, relPath)
	}
	if strings.HasPrefix(oldPath, e.plan.Config.OldStatePath) {
		relPath := strings.TrimPrefix(oldPath, e.plan.Config.OldStatePath)
		return filepath.Join(e.plan.Config.NewStatePath, relPath)
	}
	return ""
}

func (e *MigrationExecutor) completeMigration(success bool, err error) (*MigrationResult, error) {
	e.result.EndTime = time.Now()
	e.result.Duration = e.result.EndTime.Sub(e.result.StartTime)
	e.result.Success = success
	e.result.RollbackAvailable = success && e.result.BackupPath != ""

	if err != nil {
		e.result.Errors = append(e.result.Errors, err.Error())
	}

	if success {
		fmt.Printf("[INFO] [agent] [migration] completed duration=%v\n", e.result.Duration)
		if e.result.RollbackAvailable {
			fmt.Printf("[INFO] [agent] [migration] rollback_available path=%s\n", e.result.BackupPath)
		}

		// Clean up old directories after successful migration
		if err := e.stateManager.CleanupOldDirectories(); err != nil {
			fmt.Printf("[MIGRATION] Warning: Failed to cleanup old directories: %v\n", err)
		}
	} else {
		fmt.Printf("[ERROR] [agent] [migration] failed duration=%v\n", e.result.Duration)
		if len(e.result.Errors) > 0 {
			for _, errMsg := range e.result.Errors {
				fmt.Printf("[MIGRATION] Error: %s\n", errMsg)
			}
		}
	}

	return e.result, err
}

// Utility functions

func contains(slice []string, item string) bool {
	for _, s := range slice {
		if s == item {
			return true
		}
	}
	return false
}

func copyFile(src, dst string) error {
	sourceFile, err := os.Open(src)
	if err != nil {
		return err
	}
	defer sourceFile.Close()

	destFile, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer destFile.Close()

	_, err = destFile.ReadFrom(sourceFile)
	if err != nil {
		return err
	}

	// Preserve file permissions
	sourceInfo, err := os.Stat(src)
	if err != nil {
		return err
	}

	return os.Chmod(dst, sourceInfo.Mode())
}