package backup

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

	"github.com/0xJacky/Nginx-UI/internal/notification"
	"github.com/0xJacky/Nginx-UI/model"
	"github.com/0xJacky/Nginx-UI/query"
	"github.com/uozi-tech/cosy"
	"github.com/uozi-tech/cosy/logger"
)

// ExecutionResult contains the result of a backup execution
type ExecutionResult struct {
	FilePath string // Path to the created backup file
	KeyPath  string // Path to the encryption key file (if applicable)
}

// ExecuteAutoBackup executes an automatic backup task based on the configuration.
// This function handles all types of backup operations and manages the backup status
// throughout the execution process.
//
// Parameters:
//   - autoBackup: The auto backup configuration to execute
//
// Returns:
//   - error: CosyError if backup execution fails, nil if successful
func ExecuteAutoBackup(autoBackup *model.AutoBackup) error {
	logger.Infof("Starting auto backup task: %s (ID: %d, Type: %s, Storage: %s)",
		autoBackup.GetName(), autoBackup.ID, autoBackup.BackupType, autoBackup.StorageType)

	// Validate storage configuration before starting backup
	if err := validateStorageConfiguration(autoBackup); err != nil {
		logger.Errorf("Storage configuration validation failed for task %s: %v", autoBackup.Name, err)
		_ = updateBackupStatus(autoBackup.ID, model.BackupStatusFailed, err.Error())
		// Send validation failure notification
		notification.Error("Auto Backup Configuration Error",
			"Storage configuration validation failed for backup task %{backup_name}, error: %{error}",
			map[string]interface{}{
				"backup_id":   autoBackup.ID,
				"backup_name": autoBackup.Name,
				"error":       err.Error(),
			},
		)
		return err
	}

	// Update backup status to pending
	if err := updateBackupStatus(autoBackup.ID, model.BackupStatusPending, ""); err != nil {
		logger.Errorf("Failed to update backup status to pending: %v", err)
		return cosy.WrapErrorWithParams(ErrAutoBackupWriteFile, err.Error())
	}

	// Execute backup based on type
	result, backupErr := executeBackupByType(autoBackup)

	// Update backup status based on execution result
	now := time.Now()
	if backupErr != nil {
		logger.Errorf("Auto backup task %s failed: %v", autoBackup.Name, backupErr)
		if updateErr := updateBackupStatusWithTime(autoBackup.ID, model.BackupStatusFailed, backupErr.Error(), &now); updateErr != nil {
			logger.Errorf("Failed to update backup status to failed: %v", updateErr)
		}
		// Send failure notification
		notification.Error("Auto Backup Failed",
			"Backup task %{backup_name} failed to execute, error: %{error}",
			map[string]interface{}{
				"backup_id":   autoBackup.ID,
				"backup_name": autoBackup.Name,
				"error":       backupErr.Error(),
			},
		)
		return backupErr
	}

	// Handle storage upload based on storage type
	if uploadErr := handleBackupStorage(autoBackup, result); uploadErr != nil {
		logger.Errorf("Auto backup storage upload failed for task %s: %v", autoBackup.Name, uploadErr)
		if updateErr := updateBackupStatusWithTime(autoBackup.ID, model.BackupStatusFailed, uploadErr.Error(), &now); updateErr != nil {
			logger.Errorf("Failed to update backup status to failed: %v", updateErr)
		}
		// Send storage failure notification
		notification.Error("Auto Backup Storage Failed",
			"Backup task %{backup_name} failed during storage upload, error: %{error}",
			map[string]interface{}{
				"backup_id":   autoBackup.ID,
				"backup_name": autoBackup.Name,
				"error":       uploadErr.Error(),
				"timestamp":   now,
			},
		)
		return uploadErr
	}

	logger.Infof("Auto backup task %s completed successfully, file: %s", autoBackup.Name, result.FilePath)
	if updateErr := updateBackupStatusWithTime(autoBackup.ID, model.BackupStatusSuccess, "", &now); updateErr != nil {
		logger.Errorf("Failed to update backup status to success: %v", updateErr)
	}

	// Send success notification
	notification.Success("Auto Backup Completed",
		"Backup task %{backup_name} completed successfully, file: %{file_path}",
		map[string]interface{}{
			"backup_id":   autoBackup.ID,
			"backup_name": autoBackup.Name,
			"file_path":   result.FilePath,
		},
	)

	return nil
}

// executeBackupByType executes the backup operation based on the backup type.
// This function centralizes the backup type routing logic.
//
// Parameters:
//   - autoBackup: The auto backup configuration
//
// Returns:
//   - BackupExecutionResult: Result containing file paths
//   - error: CosyError if backup fails
func executeBackupByType(autoBackup *model.AutoBackup) (*ExecutionResult, error) {
	switch autoBackup.BackupType {
	case model.BackupTypeNginxAndNginxUI:
		return createEncryptedBackup(autoBackup)
	case model.BackupTypeCustomDir:
		return createCustomDirectoryBackup(autoBackup)
	default:
		return nil, cosy.WrapErrorWithParams(ErrAutoBackupUnsupportedType, string(autoBackup.BackupType))
	}
}

// createEncryptedBackup creates an encrypted backup for Nginx/Nginx UI configurations.
// This function handles all configuration backup types that require encryption.
//
// Parameters:
//   - autoBackup: The auto backup configuration
//   - backupPrefix: Prefix for the backup filename
//
// Returns:
//   - BackupExecutionResult: Result containing file paths
//   - error: CosyError if backup creation fails
func createEncryptedBackup(autoBackup *model.AutoBackup) (*ExecutionResult, error) {
	// Generate unique filename with timestamp
	filename := fmt.Sprintf("%s_%d.zip", autoBackup.GetName(), time.Now().Unix())

	// Determine output path based on storage type
	var outputPath string
	if autoBackup.StorageType == model.StorageTypeS3 {
		// For S3 storage, create temporary file
		tempDir := os.TempDir()
		outputPath = filepath.Join(tempDir, filename)
	} else {
		// For local storage, use the configured storage path
		outputPath = filepath.Join(autoBackup.StoragePath, filename)
	}

	// Create backup using the main backup function
	backupResult, err := Backup()
	if err != nil {
		return nil, cosy.WrapErrorWithParams(ErrBackupNginx, err.Error())
	}

	// Write encrypted backup content to file
	if err := writeBackupFile(outputPath, backupResult.BackupContent); err != nil {
		return nil, err
	}

	// Create and write encryption key file
	keyPath := outputPath + ".key"
	if err := writeKeyFile(keyPath, backupResult.AESKey, backupResult.AESIv); err != nil {
		return nil, err
	}

	return &ExecutionResult{
		FilePath: outputPath,
		KeyPath:  keyPath,
	}, nil
}

// createCustomDirectoryBackup creates an unencrypted backup of a custom directory.
// This function handles custom directory backups which are stored as plain ZIP files.
//
// Parameters:
//   - autoBackup: The auto backup configuration
//
// Returns:
//   - BackupExecutionResult: Result containing file paths
//   - error: CosyError if backup creation fails
func createCustomDirectoryBackup(autoBackup *model.AutoBackup) (*ExecutionResult, error) {
	// Validate that backup path is specified for custom directory backup
	if autoBackup.BackupPath == "" {
		return nil, ErrAutoBackupPathRequired
	}

	// Validate backup source path
	if err := ValidateBackupPath(autoBackup.BackupPath); err != nil {
		return nil, err
	}

	// Generate unique filename with timestamp
	filename := fmt.Sprintf("custom_dir_%s_%d.zip", autoBackup.GetName(), time.Now().Unix())

	// Determine output path based on storage type
	var outputPath string
	if autoBackup.StorageType == model.StorageTypeS3 {
		// For S3 storage, create temporary file
		tempDir := os.TempDir()
		outputPath = filepath.Join(tempDir, filename)
	} else {
		// For local storage, use the configured storage path
		outputPath = filepath.Join(autoBackup.StoragePath, filename)
	}

	// Create unencrypted ZIP archive of the custom directory
	if err := createZipArchive(outputPath, autoBackup.BackupPath); err != nil {
		return nil, cosy.WrapErrorWithParams(ErrCreateZipArchive, err.Error())
	}

	return &ExecutionResult{
		FilePath: outputPath,
		KeyPath:  "", // No key file for unencrypted backups
	}, nil
}

// writeBackupFile writes backup content to the specified file path with proper permissions.
// This function ensures backup files are created with secure permissions.
//
// Parameters:
//   - filePath: Destination file path
//   - content: Backup content to write
//
// Returns:
//   - error: CosyError if file writing fails
func writeBackupFile(filePath string, content []byte) error {
	if err := os.WriteFile(filePath, content, 0600); err != nil {
		return cosy.WrapErrorWithParams(ErrAutoBackupWriteFile, err.Error())
	}
	return nil
}

// writeKeyFile writes encryption key information to a key file.
// This function creates a key file containing AES key and IV for encrypted backups.
//
// Parameters:
//   - keyPath: Path for the key file
//   - aesKey: Base64 encoded AES key
//   - aesIv: Base64 encoded AES initialization vector
//
// Returns:
//   - error: CosyError if key file writing fails
func writeKeyFile(keyPath, aesKey, aesIv string) error {
	keyContent := fmt.Sprintf("%s:%s", aesKey, aesIv)
	if err := os.WriteFile(keyPath, []byte(keyContent), 0600); err != nil {
		return cosy.WrapErrorWithParams(ErrAutoBackupWriteKeyFile, err.Error())
	}
	return nil
}

// updateBackupStatus updates the backup status in the database.
// This function provides a centralized way to update backup execution status.
//
// Parameters:
//   - id: Auto backup configuration ID
//   - status: New backup status
//   - errorMsg: Error message (empty for successful backups)
//
// Returns:
//   - error: Database error if update fails
func updateBackupStatus(id uint64, status model.BackupStatus, errorMsg string) error {
	_, err := query.AutoBackup.Where(query.AutoBackup.ID.Eq(id)).Updates(map[string]interface{}{
		"last_backup_status": status,
		"last_backup_error":  errorMsg,
	})
	return err
}

// updateBackupStatusWithTime updates the backup status and timestamp in the database.
// This function updates both status and execution time for completed backup operations.
//
// Parameters:
//   - id: Auto backup configuration ID
//   - status: New backup status
//   - errorMsg: Error message (empty for successful backups)
//   - backupTime: Timestamp of the backup execution
//
// Returns:
//   - error: Database error if update fails
func updateBackupStatusWithTime(id uint64, status model.BackupStatus, errorMsg string, backupTime *time.Time) error {
	_, err := query.AutoBackup.Where(query.AutoBackup.ID.Eq(id)).Updates(map[string]interface{}{
		"last_backup_status": status,
		"last_backup_error":  errorMsg,
		"last_backup_time":   backupTime,
	})
	return err
}

// GetEnabledAutoBackups retrieves all enabled auto backup configurations from the database.
// This function is used by the cron scheduler to get active backup tasks.
//
// Returns:
//   - []*model.AutoBackup: List of enabled auto backup configurations
//   - error: Database error if query fails
func GetEnabledAutoBackups() ([]*model.AutoBackup, error) {
	return query.AutoBackup.Where(query.AutoBackup.Enabled.Is(true)).Find()
}

// GetAutoBackupByID retrieves a specific auto backup configuration by its ID.
// This function provides access to individual backup configurations.
//
// Parameters:
//   - id: Auto backup configuration ID
//
// Returns:
//   - *model.AutoBackup: The auto backup configuration
//   - error: Database error if query fails or record not found
func GetAutoBackupByID(id uint64) (*model.AutoBackup, error) {
	return query.AutoBackup.Where(query.AutoBackup.ID.Eq(id)).First()
}

// validateStorageConfiguration validates the storage configuration based on storage type.
// This function centralizes storage validation logic for both local and S3 storage.
//
// Parameters:
//   - autoBackup: The auto backup configuration to validate
//
// Returns:
//   - error: CosyError if validation fails, nil if configuration is valid
func validateStorageConfiguration(autoBackup *model.AutoBackup) error {
	switch autoBackup.StorageType {
	case model.StorageTypeLocal:
		// For local storage, validate the storage path
		return ValidateStoragePath(autoBackup.StoragePath)
	case model.StorageTypeS3:
		// For S3 storage, test the connection
		s3Client, err := NewS3Client(autoBackup)
		if err != nil {
			return err
		}
		return s3Client.TestS3Connection(context.Background())
	default:
		return cosy.WrapErrorWithParams(ErrAutoBackupUnsupportedType, string(autoBackup.StorageType))
	}
}

// handleBackupStorage handles the storage of backup files based on storage type.
// This function routes backup storage to the appropriate handler (local or S3).
//
// Parameters:
//   - autoBackup: The auto backup configuration
//   - result: The backup execution result containing file paths
//
// Returns:
//   - error: CosyError if storage operation fails
func handleBackupStorage(autoBackup *model.AutoBackup, result *ExecutionResult) error {
	switch autoBackup.StorageType {
	case model.StorageTypeLocal:
		// For local storage, files are already written to the correct location
		logger.Infof("Backup files stored locally: %s", result.FilePath)
		return nil
	case model.StorageTypeS3:
		// For S3 storage, upload files to S3 and optionally clean up local files
		return handleS3Storage(autoBackup, result)
	default:
		return cosy.WrapErrorWithParams(ErrAutoBackupUnsupportedType, string(autoBackup.StorageType))
	}
}

// handleS3Storage handles S3 storage operations for backup files.
// This function uploads backup files to S3 and manages local file cleanup.
//
// Parameters:
//   - autoBackup: The auto backup configuration
//   - result: The backup execution result containing file paths
//
// Returns:
//   - error: CosyError if S3 operations fail
func handleS3Storage(autoBackup *model.AutoBackup, result *ExecutionResult) error {
	// Create S3 client
	s3Client, err := NewS3Client(autoBackup)
	if err != nil {
		return err
	}

	// Upload backup files to S3
	ctx := context.Background()
	if err := s3Client.UploadBackupFiles(ctx, result, autoBackup); err != nil {
		return err
	}

	// Clean up local files after successful S3 upload
	if err := cleanupLocalBackupFiles(result); err != nil {
		logger.Warnf("Failed to cleanup local backup files: %v", err)
		// Don't return error for cleanup failure as the backup was successful
	}

	logger.Infof("Backup files successfully uploaded to S3 and local files cleaned up")
	return nil
}

// cleanupLocalBackupFiles removes local backup files after successful S3 upload.
// This function helps manage disk space by removing temporary local files.
//
// Parameters:
//   - result: The backup execution result containing file paths to clean up
//
// Returns:
//   - error: Standard error if cleanup fails
func cleanupLocalBackupFiles(result *ExecutionResult) error {
	// Remove backup file
	if err := os.Remove(result.FilePath); err != nil && !os.IsNotExist(err) {
		return fmt.Errorf("failed to remove backup file %s: %v", result.FilePath, err)
	}

	// Remove key file if it exists
	if result.KeyPath != "" {
		if err := os.Remove(result.KeyPath); err != nil && !os.IsNotExist(err) {
			return fmt.Errorf("failed to remove key file %s: %v", result.KeyPath, err)
		}
	}

	return nil
}

// ValidateAutoBackupConfig performs comprehensive validation of auto backup configuration.
// This function centralizes all validation logic for both creation and modification.
//
// Parameters:
//   - config: Auto backup configuration to validate
//
// Returns:
//   - error: CosyError if validation fails, nil if configuration is valid
func ValidateAutoBackupConfig(config *model.AutoBackup) error {
	// Validate backup path for custom directory backup type
	if config.BackupType == model.BackupTypeCustomDir {
		if config.BackupPath == "" {
			return ErrAutoBackupPathRequired
		}

		// Use centralized path validation from backup package
		if err := ValidateBackupPath(config.BackupPath); err != nil {
			return err
		}
	}

	// Validate storage path using centralized validation
	if config.StorageType == model.StorageTypeLocal && config.StoragePath != "" {
		if err := ValidateStoragePath(config.StoragePath); err != nil {
			return err
		}
	}

	// Validate S3 configuration if storage type is S3
	if config.StorageType == model.StorageTypeS3 {
		if err := ValidateS3Config(config); err != nil {
			return err
		}
	}

	return nil
}

// ValidateS3Config validates S3 storage configuration completeness.
// This function ensures all required S3 fields are provided when S3 storage is selected.
//
// Parameters:
//   - config: Auto backup configuration with S3 settings
//
// Returns:
//   - error: CosyError if S3 configuration is incomplete, nil if valid
func ValidateS3Config(config *model.AutoBackup) error {
	var missingFields []string

	// Check required S3 fields
	if config.S3Bucket == "" {
		missingFields = append(missingFields, "bucket")
	}
	if config.S3AccessKeyID == "" {
		missingFields = append(missingFields, "access_key_id")
	}
	if config.S3SecretAccessKey == "" {
		missingFields = append(missingFields, "secret_access_key")
	}

	// Return error if any required fields are missing
	if len(missingFields) > 0 {
		return cosy.WrapErrorWithParams(ErrAutoBackupS3ConfigIncomplete, strings.Join(missingFields, ", "))
	}

	return nil
}

// RestoreAutoBackup restores a soft-deleted auto backup configuration.
// This function restores the backup configuration and re-registers the cron job if enabled.
//
// Parameters:
//   - id: Auto backup configuration ID to restore
//
// Returns:
//   - error: Database error if restore fails
func RestoreAutoBackup(id uint64) error {
	// Restore the soft-deleted record
	_, err := query.AutoBackup.Unscoped().Where(query.AutoBackup.ID.Eq(id)).Update(query.AutoBackup.DeletedAt, nil)
	if err != nil {
		return err
	}

	// Get the restored backup configuration
	autoBackup, err := GetAutoBackupByID(id)
	if err != nil {
		return err
	}

	// Re-register cron job if the backup is enabled
	if autoBackup.Enabled {
		// Import cron package to register the job
		// Note: This would require importing the cron package, which might create circular dependency
		// The actual implementation should be handled at the API level
		logger.Infof("Auto backup %d restored and needs cron job registration", id)
	}

	return nil
}
