package migrations

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"time"

	"github.com/go-gormigrate/gormigrate/v2"
	"gorm.io/gorm"
)

// AlterBooleanDefaults changes boolean columns with default:true to pointers
// using explicit table recreation with raw SQL for SQLite compatibility.
func AlterBooleanDefaults() *gormigrate.Migration {

	// --- Raw SQL CREATE TABLE statements for the target schema ---

	const createNotificationServicesSQL = `
	CREATE TABLE notification_services (
		id INTEGER PRIMARY KEY AUTOINCREMENT,
		name TEXT NOT NULL,
		type TEXT NOT NULL,
		is_enabled INTEGER DEFAULT 1, -- Target: *bool, SQLite uses 0/1, default true
		config TEXT,
		description TEXT,
		event_triggers TEXT DEFAULT '[]',
		payload_template TEXT,
		secret_key TEXT,
		retry_policy TEXT DEFAULT 'simple',
		last_used timestamp,
		success_count INTEGER DEFAULT 0,
		failure_count INTEGER DEFAULT 0,
		created_by INTEGER,
		created_at timestamp,
		updated_at timestamp
	);`

	const createAuthProvidersSQL = `
	CREATE TABLE auth_providers (
		id INTEGER PRIMARY KEY AUTOINCREMENT,
		name TEXT NOT NULL,
		type TEXT NOT NULL,
		enabled INTEGER DEFAULT 1, -- Target: *bool, SQLite uses 0/1, default true
		description TEXT,
		provider_url TEXT,
		client_id TEXT,
		client_secret TEXT,
		redirect_url TEXT,
		scopes TEXT,
		attribute_mapping TEXT,
		config TEXT,
		icon_url TEXT,
		successful_logins INTEGER DEFAULT 0,
		last_used timestamp,
		created_at timestamp,
		updated_at timestamp
	);`

	const createTransferConfigsSQL = `
	CREATE TABLE transfer_configs (
		id INTEGER PRIMARY KEY AUTOINCREMENT,
		name TEXT NOT NULL,
		source_type TEXT NOT NULL,
		source_path TEXT NOT NULL,
		source_host TEXT,
		source_port INTEGER DEFAULT 22,
		source_user TEXT,
		source_key_file TEXT,
		source_bucket TEXT,
		source_region TEXT,
		source_access_key TEXT,
		source_endpoint TEXT,
		source_share TEXT,
		source_domain TEXT,
		source_passive_mode INTEGER DEFAULT 1, -- Already *bool, keep default
		source_client_id TEXT,
		source_drive_id TEXT,
		source_team_drive TEXT,
		source_read_only INTEGER,
		source_start_year INTEGER,
		source_include_archived INTEGER,
		file_pattern TEXT DEFAULT '*',
		output_pattern TEXT,
		destination_type TEXT NOT NULL,
		destination_path TEXT NOT NULL,
		dest_host TEXT,
		dest_port INTEGER DEFAULT 22,
		dest_user TEXT,
		dest_key_file TEXT,
		dest_bucket TEXT,
		dest_region TEXT,
		dest_access_key TEXT,
		dest_endpoint TEXT,
		dest_share TEXT,
		dest_domain TEXT,
		dest_passive_mode INTEGER DEFAULT 1, -- Already *bool, keep default
		dest_client_id TEXT,
		dest_drive_id TEXT,
		dest_team_drive TEXT,
		dest_read_only INTEGER,
		dest_start_year INTEGER,
		dest_include_archived INTEGER,
		use_builtin_auth_source INTEGER,
		use_builtin_auth_dest INTEGER,
		google_drive_authenticated INTEGER,
		archive_path TEXT,
		archive_enabled INTEGER DEFAULT 0,
		rclone_flags TEXT,
		command_id INTEGER DEFAULT 1,
		command_flags TEXT,
		command_flag_values TEXT,
		delete_after_transfer INTEGER DEFAULT 0,
		skip_processed_files INTEGER DEFAULT 1, -- Target: *bool, SQLite uses 0/1, default true
		max_concurrent_transfers INTEGER DEFAULT 4,
		created_by INTEGER,
		created_at timestamp,
		updated_at timestamp
	);`

	// --- End Raw SQL ---

	return &gormigrate.Migration{
		ID: "012_alter_boolean_defaults",
		Migrate: func(tx *gorm.DB) error {
			// --- Backup Logic (copied) ---
			var count int64
			if err := tx.Raw("SELECT count(*) FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'").Scan(&count).Error; err != nil {
				return fmt.Errorf("failed to check for existing tables: %v", err)
			}
			if count > 0 {
				sqlDB, err := tx.DB()
				if err != nil {
					return fmt.Errorf("failed to get underlying database: %v", err)
				}
				var seq int
				var name, dbPath string
				if err := sqlDB.QueryRow("PRAGMA database_list").Scan(&seq, &name, &dbPath); err != nil {
					return fmt.Errorf("failed to get database path: %v", err)
				}
				backupDir := os.Getenv("BACKUP_DIR")
				if backupDir == "" {
					backupDir = "/app/backups"
					if _, err := os.Stat(backupDir); os.IsNotExist(err) {
						backupDir = "backups"
					}
				}
				if err := os.MkdirAll(backupDir, 0755); err != nil {
					return fmt.Errorf("failed to create backup directory: %v", err)
				}
				dbFileName := filepath.Base(dbPath)
				backupFileName := fmt.Sprintf("%s.backup.%s", dbFileName, time.Now().Format("20060102_150405"))
				backupFile := filepath.Join(backupDir, backupFileName)
				data, err := os.ReadFile(dbPath)
				if err != nil {
					return fmt.Errorf("failed to read database for backup: %v", err)
				}
				if err := os.WriteFile(backupFile, data, 0600); err != nil {
					return fmt.Errorf("failed to create database backup: %v", err)
				}
				fmt.Printf("Created database backup at: %s\n", backupFile)
			}
			// --- End Backup Logic ---

			// --- Table Recreation Logic for SQLite ---
			if err := tx.Exec("PRAGMA foreign_keys = OFF").Error; err != nil {
				return fmt.Errorf("failed to disable foreign keys: %w", err)
			}
			defer func() {
				if err := tx.Exec("PRAGMA foreign_keys = ON").Error; err != nil {
					fmt.Printf("Warning: failed to re-enable foreign keys: %v\n", err)
				}
			}()

			// Helper function for table recreation
			recreateTable := func(tableName, createSQL string) error {
				fmt.Printf("Recreating table %s...\n", tableName)
				oldTableName := fmt.Sprintf("_%s_old", tableName)

				// Drop the old temp table if it exists from a previous failed run
				if err := tx.Exec(fmt.Sprintf("DROP TABLE IF EXISTS %s", oldTableName)).Error; err != nil {
					// Log the error but proceed, as the rename might still work or fail for the intended reason
					fmt.Printf("Warning: failed to drop potential leftover table %s: %v\n", oldTableName, err)
				}

				// Rename old table
				if err := tx.Exec(fmt.Sprintf("ALTER TABLE %s RENAME TO %s", tableName, oldTableName)).Error; err == nil {
					fmt.Printf("Renamed %s to %s.\n", tableName, oldTableName)

					// Create new table using raw SQL
					fmt.Printf("Creating new %s table...\n", tableName)
					if err := tx.Exec(createSQL).Error; err != nil {
						return fmt.Errorf("failed to create new %s table: %w", tableName, err)
					}
					fmt.Printf("New %s table created.\n", tableName)

					// Copy data
					fmt.Printf("Copying data to new %s table...\n", tableName)
					// IMPORTANT: Ensure column order/names match if schema changed beyond types/defaults
					if err := tx.Exec(fmt.Sprintf("INSERT INTO %s SELECT * FROM %s", tableName, oldTableName)).Error; err != nil {
						return fmt.Errorf("failed to copy data to new %s table: %w", tableName, err)
					}
					fmt.Printf("Data copied to %s.\n", tableName)

					// Drop old table
					if err := tx.Exec(fmt.Sprintf("DROP TABLE %s", oldTableName)).Error; err != nil {
						return fmt.Errorf("failed to drop old %s table: %w", tableName, err)
					}
					fmt.Printf("Successfully recreated %s.\n", tableName)
				} else {
					// Check if rename failed because table doesn't exist (fresh install)
					var tableExists int
					tx.Raw(fmt.Sprintf("SELECT count(*) FROM sqlite_master WHERE type='table' AND name='%s'", tableName)).Scan(&tableExists)
					if tableExists == 0 {
						fmt.Printf("%s table does not exist, creating.\n", tableName)
						if err := tx.Exec(createSQL).Error; err != nil { // Create table directly
							return fmt.Errorf("failed to create new %s table: %w", tableName, err)
						}
					} else {
						return fmt.Errorf("failed to rename %s: %w", tableName, err) // Real rename error
					}
				}
				return nil
			}

			// Recreate tables
			if err := recreateTable("notification_services", createNotificationServicesSQL); err != nil {
				return err
			}
			if err := recreateTable("auth_providers", createAuthProvidersSQL); err != nil {
				return err
			}
			if err := recreateTable("transfer_configs", createTransferConfigsSQL); err != nil {
				return err
			}

			// --- End Table Recreation Logic ---

			// Create audit log entry
			now := time.Now()
			details, auditErr := json.Marshal(map[string]interface{}{
				"tables_affected": []string{"notification_services", "auth_providers", "transfer_configs"},
				"columns_altered": []string{"is_enabled", "enabled", "skip_processed_files"},
				"new_type":        "*bool (pointer to boolean)",
				"method":          "Table recreation (SQLite - Raw SQL)",
				"message":         "Changed boolean columns with default:true to pointers to handle false values correctly with GORM.",
			})
			if auditErr != nil {
				fmt.Printf("Warning: Failed to marshal audit log details: %v\n", auditErr)
			}

			if auditErr == nil {
				if auditExecErr := tx.Exec(`
					INSERT INTO audit_logs (action, entity_type, entity_id, user_id, details, created_at, updated_at, timestamp)
					VALUES ('schema_update', 'multiple_tables', 0, 1, ?, ?, ?, ?)
				`, string(details), now, now, now).Error; auditExecErr != nil {
					fmt.Printf("Warning: Failed to insert audit log: %v\n", auditExecErr)
				}
			}

			return nil
		},
		Rollback: func(tx *gorm.DB) error {
			// Rollback is complex and risky with table recreation. Log skip.
			now := time.Now()
			details, err := json.Marshal(map[string]interface{}{
				"migration_id": "012_alter_boolean_defaults",
				"message":      "Skipping rollback of boolean column type changes (via table recreation) due to complexity/potential data loss.",
			})
			if err != nil {
				fmt.Printf("Warning: Failed to marshal rollback audit log details: %v\n", err)
			}

			if err == nil {
				if auditExecErr := tx.Exec(`
					INSERT INTO audit_logs (action, entity_type, entity_id, user_id, details, created_at, updated_at, timestamp)
					VALUES ('migration_rollback', 'multiple_tables', 0, 1, ?, ?, ?, ?)
				`, string(details), now, now, now).Error; auditExecErr != nil {
					fmt.Printf("Warning: Failed to insert rollback audit log: %v\n", auditExecErr)
				}
			}
			fmt.Println("Rollback for migration 012_alter_boolean_defaults skipped for safety.")
			return nil
		},
	}
}
