package validation

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/migration/pathutils"
	"github.com/Fimeg/RedFlag/aggregator-agent/internal/models"
	"github.com/google/uuid"
)

// FileValidator handles comprehensive file validation for migration
type FileValidator struct {
	pathManager *pathutils.PathManager
	eventBuffer *event.Buffer
	agentID     uuid.UUID
}

// NewFileValidator creates a new file validator
func NewFileValidator(pm *pathutils.PathManager, eventBuffer *event.Buffer, agentID uuid.UUID) *FileValidator {
	return &FileValidator{
		pathManager: pm,
		eventBuffer: eventBuffer,
		agentID:     agentID,
	}
}

// ValidationResult holds validation results
type ValidationResult struct {
	Valid      bool              `json:"valid"`
	Errors     []string          `json:"errors"`
	Warnings   []string          `json:"warnings"`
	Inventory  *FileInventory    `json:"inventory"`
	Statistics *ValidationStats  `json:"statistics"`
}

// FileInventory represents validated files
type FileInventory struct {
	ValidFiles     []common.AgentFile `json:"valid_files"`
	InvalidFiles   []InvalidFile      `json:"invalid_files"`
	MissingFiles   []string           `json:"missing_files"`
	SkippedFiles   []SkippedFile      `json:"skipped_files"`
	Directories    []string           `json:"directories"`
}

// InvalidFile represents a file that failed validation
type InvalidFile struct {
	Path        string `json:"path"`
	Reason      string `json:"reason"`
	ErrorType   string `json:"error_type"` // "not_found", "permission", "traversal", "other"
	Expected    string `json:"expected"`
}

// SkippedFile represents a file that was intentionally skipped
type SkippedFile struct {
	Path   string `json:"path"`
	Reason string `json:"reason"`
}

// ValidationStats holds statistics about validation
type ValidationStats struct {
	TotalFiles      int     `json:"total_files"`
	ValidFiles      int     `json:"valid_files"`
	InvalidFiles    int     `json:"invalid_files"`
	MissingFiles    int     `json:"missing_files"`
	SkippedFiles    int     `json:"skipped_files"`
	ValidationTime  int64   `json:"validation_time_ms"`
	TotalSizeBytes  int64   `json:"total_size_bytes"`
}

// ValidateInventory performs comprehensive validation of file inventory
func (v *FileValidator) ValidateInventory(files []common.AgentFile, requiredPatterns []string) (*ValidationResult, error) {
	start := time.Now()
	result := &ValidationResult{
		Valid:     true,
		Errors:    []string{},
		Warnings:  []string{},
		Inventory: &FileInventory{
			ValidFiles:   []common.AgentFile{},
			InvalidFiles: []InvalidFile{},
			MissingFiles: []string{},
			SkippedFiles: []SkippedFile{},
			Directories:  []string{},
		},
		Statistics: &ValidationStats{},
	}

	// Group files by directory and collect statistics
	dirMap := make(map[string]bool)
	var totalSize int64

	for _, file := range files {
		result.Statistics.TotalFiles++

		// Skip log files (.log, .tmp) as they shouldn't be migrated
		if containsAny(file.Path, []string{"*.log", "*.tmp"}) {
			result.Inventory.SkippedFiles = append(result.Inventory.SkippedFiles, SkippedFile{
				Path:   file.Path,
				Reason: "Log/temp files are not migrated",
			})
			result.Statistics.SkippedFiles++
			continue
		}

		// Validate file path and existence
		if err := v.pathManager.ValidatePath(file.Path); err != nil {
			result.Valid = false
			result.Statistics.InvalidFiles++

			errorType := "other"
			reason := err.Error()
			if os.IsNotExist(err) {
				errorType = "not_found"
				reason = fmt.Sprintf("File does not exist: %s", file.Path)
			} else if os.IsPermission(err) {
				errorType = "permission"
				reason = fmt.Sprintf("Permission denied: %s", file.Path)
			}

			result.Errors = append(result.Errors, reason)
			result.Inventory.InvalidFiles = append(result.Inventory.InvalidFiles, InvalidFile{
				Path:      file.Path,
				Reason:    reason,
				ErrorType: errorType,
			})

			// Log the validation failure
			v.bufferEvent("file_validation_failed", "warning", "migration_validator",
				reason,
				map[string]interface{}{
					"file_path": file.Path,
					"error_type": errorType,
					"file_size": file.Size,
				})
			continue
		}

		// Track directory
		dir := filepath.Dir(file.Path)
		if !dirMap[dir] {
			dirMap[dir] = true
			result.Inventory.Directories = append(result.Inventory.Directories, dir)
		}

		result.Inventory.ValidFiles = append(result.Inventory.ValidFiles, file)
		result.Statistics.ValidFiles++
		totalSize += file.Size
	}

	result.Statistics.TotalSizeBytes = totalSize

	// Check for required files
	for _, pattern := range requiredPatterns {
		found := false
		for _, file := range result.Inventory.ValidFiles {
			if matched, _ := filepath.Match(pattern, filepath.Base(file.Path)); matched {
				found = true
				break
			}
		}
		if !found {
			result.Valid = false
			missing := fmt.Sprintf("Required file pattern not found: %s", pattern)
			result.Errors = append(result.Errors, missing)
			result.Inventory.MissingFiles = append(result.Inventory.MissingFiles, pattern)
			result.Statistics.MissingFiles++

			// Log missing required file
			v.bufferEvent("required_file_missing", "error", "migration_validator",
				missing,
				map[string]interface{}{
					"required_pattern": pattern,
					"phase": "validation",
				})
		}
	}

	result.Statistics.ValidationTime = time.Since(start).Milliseconds()

	// Log validation completion
	v.bufferEvent("validation_completed", "info", "migration_validator",
		fmt.Sprintf("File validation completed: %d total, %d valid, %d invalid, %d skipped",
			result.Statistics.TotalFiles,
			result.Statistics.ValidFiles,
			result.Statistics.InvalidFiles,
			result.Statistics.SkippedFiles),
		map[string]interface{}{
			"stats": result.Statistics,
			"valid": result.Valid,
		})

	return result, nil
}

// ValidateBackupLocation validates backup location is writable and safe
func (v *FileValidator) ValidateBackupLocation(backupPath string) error {
	// Normalize path
	normalized, err := v.pathManager.NormalizeToAbsolute(backupPath)
	if err != nil {
		return fmt.Errorf("invalid backup path: %w", err)
	}

	// Ensure backup path isn't in system directories
	if strings.HasPrefix(normalized, "/bin/") || strings.HasPrefix(normalized, "/sbin/") ||
		strings.HasPrefix(normalized, "/usr/bin/") || strings.HasPrefix(normalized, "/usr/sbin/") {
		return fmt.Errorf("backup path cannot be in system directory: %s", normalized)
	}

	// Ensure parent directory exists and is writable
	parent := filepath.Dir(normalized)
	if err := v.pathManager.EnsureDirectory(parent); err != nil {
		return fmt.Errorf("cannot create backup directory: %w", err)
	}

	// Test write permission (create a temp file)
	testFile := filepath.Join(parent, ".migration_test_"+uuid.New().String()[:8])
	if err := os.WriteFile(testFile, []byte("test"), 0600); err != nil {
		return fmt.Errorf("backup directory not writable: %w", err)
	}

	// Clean up test file
	_ = os.Remove(testFile)

	return nil
}

// PreValidate validates all conditions before migration starts
func (v *FileValidator) PreValidate(detection *MigrationDetection, backupPath string) (*ValidationResult, error) {
	v.bufferEvent("pre_validation_started", "info", "migration_validator",
		"Starting comprehensive migration validation",
		map[string]interface{}{
			"agent_version": detection.CurrentAgentVersion,
			"config_version": detection.CurrentConfigVersion,
		})

	// Collect all files from inventory
	allFiles := v.collectAllFiles(detection.Inventory)

	// Define required patterns based on migration needs
	requiredPatterns := []string{
		"config.json", // Config is essential
		// Note: agent.key files are generated if missing
	}

	// Validate inventory
	result, err := v.ValidateInventory(allFiles, requiredPatterns)
	if err != nil {
		v.bufferEvent("validation_error", "error", "migration_validator",
			fmt.Sprintf("Validation failed: %v", err),
			map[string]interface{}{
				"error": err.Error(),
				"phase": "pre_validation",
			})
		return nil, fmt.Errorf("validation failed: %w", err)
	}

	// Validate backup location
	if err := v.ValidateBackupLocation(backupPath); err != nil {
		result.Valid = false
		result.Errors = append(result.Errors, fmt.Sprintf("Backup location invalid: %v", err))

		v.bufferEvent("backup_validation_failed", "error", "migration_validator",
			fmt.Sprintf("Backup validation failed: %v", err),
			map[string]interface{}{
				"backup_path": backupPath,
				"error": err.Error(),
				"phase": "validation",
			})
	}

	// Validate new directories can be created (but don't create them yet)
	newDirs := []string{
		v.pathManager.GetConfig().NewConfigPath,
		v.pathManager.GetConfig().NewStatePath,
	}
	for _, dir := range newDirs {
		normalized, err := v.pathManager.NormalizeToAbsolute(dir)
		if err != nil {
			result.Valid = false
			result.Errors = append(result.Errors, fmt.Sprintf("Invalid new directory %s: %v", dir, err))
			continue
		}

		// Check if parent is writable
		parent := filepath.Dir(normalized)
		if _, err := os.Stat(parent); err != nil {
			if os.IsNotExist(err) {
				result.Warnings = append(result.Warnings, fmt.Sprintf("Parent directory for %s does not exist: %s", dir, parent))
			}
		}
	}

	// Log final validation status
	v.bufferEvent("pre_validation_completed", "info", "migration_validator",
		fmt.Sprintf("Pre-validation completed: %s", func() string {
			if result.Valid {
				return "PASSED"
			}
			return "FAILED"
		}()),
		map[string]interface{}{
			"errors_count":   len(result.Errors),
			"warnings_count": len(result.Warnings),
			"files_valid":    result.Statistics.ValidFiles,
			"files_invalid":  result.Statistics.InvalidFiles,
			"files_skipped":  result.Statistics.SkippedFiles,
		})

	return result, nil
}

// collectAllFiles collects all files from the migration inventory
func (v *FileValidator) collectAllFiles(inventory *AgentFileInventory) []common.AgentFile {
	var allFiles []common.AgentFile
	if inventory != nil {
		allFiles = append(allFiles, inventory.ConfigFiles...)
		allFiles = append(allFiles, inventory.StateFiles...)
		allFiles = append(allFiles, inventory.BinaryFiles...)
		allFiles = append(allFiles, inventory.LogFiles...)
		allFiles = append(allFiles, inventory.CertificateFiles...)
	}
	return allFiles
}

// bufferEvent logs an event to the event buffer
func (v *FileValidator) bufferEvent(eventSubtype, severity, component, message string, metadata map[string]interface{}) {
	if v.eventBuffer == nil {
		return
	}

	event := &models.SystemEvent{
		ID:           uuid.New(),
		AgentID:      &v.agentID,
		EventType:    models.EventTypeAgentMigration, // Using model constant
		EventSubtype: eventSubtype,
		Severity:     severity,
		Component:    component,
		Message:      message,
		Metadata:     metadata,
		CreatedAt:    time.Now(),
	}

	if err := v.eventBuffer.BufferEvent(event); err != nil {
		fmt.Printf("[VALIDATION] Warning: Failed to buffer event: %v\n", err)
	}
}

// containsAny checks if path matches any of the patterns
func containsAny(path string, patterns []string) bool {
	for _, pattern := range patterns {
		if matched, _ := filepath.Match(pattern, filepath.Base(path)); matched {
			return true
		}
	}
	return false
}

// ValidateFileForBackup validates a single file before backup
func (v *FileValidator) ValidateFileForBackup(file common.AgentFile) error {
	// Check if file exists
	if _, err := os.Stat(file.Path); err != nil {
		if os.IsNotExist(err) {
			v.bufferEvent("backup_file_missing", "warning", "migration_validator",
				fmt.Sprintf("Skipping backup of non-existent file: %s", file.Path),
				map[string]interface{}{
					"file_path": file.Path,
					"phase": "backup",
				})
			return fmt.Errorf("file does not exist: %s", file.Path)
		}
		return fmt.Errorf("failed to access file %s: %w", file.Path, err)
	}

	// Additional validation for sensitive files
	if strings.Contains(file.Path, ".key") || strings.Contains(file.Path, "config") {
		// Key files should be readable only by owner
		info, err := os.Stat(file.Path)
		if err == nil {
			perm := info.Mode().Perm()
			// Check if others have read permission
			if perm&0004 != 0 {
				v.bufferEvent("insecure_file_permissions", "warning", "migration_validator",
					fmt.Sprintf("Sensitive file has world-readable permissions: %s (0%o)", file.Path, perm),
					map[string]interface{}{
						"file_path": file.Path,
						"permissions": perm,
					})
			}
		}
	}

	return nil
}