//! Integration tests for storage migration functionality
//!
//! These tests verify the complete migration process across different
//! storage backend combinations, including error handling and rollback
//! scenarios.

use std::sync::Arc;

use futures::stream::{self, BoxStream};
use tempfile::TempDir;
use tokio::sync::Mutex;

use super::*;
use crate::storage::{
    filesystem::FilesystemStorage,
    traits::{
        AuditEntry, AuthToken, Metric, StorageResult, StorageStatistics, TaskEvent, TaskEventType,
        TaskStorage,
    },
    StorageConfig,
};
use crate::{TaskStatus, WorkflowTask};

/// Mock storage for testing migration scenarios
#[derive(Debug)]
pub struct MockStorage {
    tasks: Arc<Mutex<Vec<WorkflowTask>>>,
    audit_entries: Arc<Mutex<Vec<AuditEntry>>>,
    metrics: Arc<Mutex<Vec<Metric>>>,
    should_fail: Arc<Mutex<bool>>,
    backup_supported: bool,
}

impl MockStorage {
    pub fn new() -> Self {
        Self {
            tasks: Arc::new(Mutex::new(Vec::new())),
            audit_entries: Arc::new(Mutex::new(Vec::new())),
            metrics: Arc::new(Mutex::new(Vec::new())),
            should_fail: Arc::new(Mutex::new(false)),
            backup_supported: true,
        }
    }

    pub fn with_backup_support(mut self, supported: bool) -> Self {
        self.backup_supported = supported;
        self
    }

    pub async fn set_should_fail(&self, should_fail: bool) {
        *self.should_fail.lock().await = should_fail;
    }

    pub async fn add_test_task(&self, id: &str, name: &str, status: TaskStatus) {
        let task = WorkflowTask {
            id: id.to_string(),
            name: name.to_string(),
            command: "echo test".to_string(),
            args: vec!["hello".to_string()],
            dependencies: vec![],
            status,
            created_at: chrono::Utc::now(),
            started_at: None,
            completed_at: None,
            output: None,
            error: None,
        };

        self.tasks.lock().await.push(task);
    }

    pub async fn task_count(&self) -> usize {
        self.tasks.lock().await.len()
    }
}

#[async_trait::async_trait]
impl TaskStorage for MockStorage {
    async fn init(&self) -> StorageResult<()> {
        let should_fail = *self.should_fail.lock().await;
        if should_fail {
            return Err(StorageError::ConnectionFailed {
                reason: "Mock initialization failure".to_string(),
            });
        }
        Ok(())
    }

    async fn health_check(&self) -> StorageResult<bool> {
        let should_fail = *self.should_fail.lock().await;
        Ok(!should_fail)
    }

    async fn enqueue(&self, task: WorkflowTask, _priority: u8) -> StorageResult<()> {
        let should_fail = *self.should_fail.lock().await;
        if should_fail {
            return Err(StorageError::BackendError(anyhow::anyhow!(
                "Mock enqueue failure"
            )));
        }

        self.tasks.lock().await.push(task);
        Ok(())
    }

    async fn enqueue_batch(&self, tasks: Vec<(WorkflowTask, u8)>) -> StorageResult<()> {
        let should_fail = *self.should_fail.lock().await;
        if should_fail {
            return Err(StorageError::BackendError(anyhow::anyhow!(
                "Mock batch enqueue failure"
            )));
        }

        let mut task_list = self.tasks.lock().await;
        for (task, _priority) in tasks {
            task_list.push(task);
        }
        Ok(())
    }

    async fn dequeue(&self) -> StorageResult<Option<WorkflowTask>> {
        Ok(self.tasks.lock().await.pop())
    }

    async fn peek(&self) -> StorageResult<Option<WorkflowTask>> {
        Ok(self.tasks.lock().await.last().cloned())
    }

    async fn get_task(&self, id: &str) -> StorageResult<Option<WorkflowTask>> {
        let tasks = self.tasks.lock().await;
        Ok(tasks.iter().find(|task| task.id == id).cloned())
    }

    async fn update_task(&self, updated_task: WorkflowTask) -> StorageResult<()> {
        let mut tasks = self.tasks.lock().await;
        if let Some(task) = tasks.iter_mut().find(|task| task.id == updated_task.id) {
            *task = updated_task;
        }
        Ok(())
    }

    async fn update_task_status(&self, id: &str, status: TaskStatus) -> StorageResult<()> {
        let mut tasks = self.tasks.lock().await;
        if let Some(task) = tasks.iter_mut().find(|task| task.id == id) {
            task.status = status;
        }
        Ok(())
    }

    async fn list_tasks(
        &self,
        status_filter: Option<TaskStatus>,
    ) -> StorageResult<Vec<WorkflowTask>> {
        let tasks = self.tasks.lock().await;
        match status_filter {
            Some(status) => Ok(tasks
                .iter()
                .filter(|task| task.status == status)
                .cloned()
                .collect()),
            None => Ok(tasks.clone()),
        }
    }

    async fn requeue_failed_task(&self, _id: &str) -> StorageResult<bool> {
        Ok(true)
    }

    async fn queue_size(&self) -> StorageResult<usize> {
        Ok(self.tasks.lock().await.len())
    }

    async fn total_tasks(&self) -> StorageResult<usize> {
        Ok(self.tasks.lock().await.len())
    }

    async fn cleanup_completed_tasks(&self, _older_than: chrono::Duration) -> StorageResult<usize> {
        let mut tasks = self.tasks.lock().await;
        let original_len = tasks.len();
        tasks.retain(|task| task.status != TaskStatus::Completed);
        Ok(original_len - tasks.len())
    }

    async fn get_audit_log(&self, _time_range: TimeRange) -> StorageResult<Vec<AuditEntry>> {
        Ok(self.audit_entries.lock().await.clone())
    }

    async fn record_audit_entry(&self, entry: AuditEntry) -> StorageResult<()> {
        self.audit_entries.lock().await.push(entry);
        Ok(())
    }

    async fn get_metrics(&self, _time_range: TimeRange) -> StorageResult<Vec<Metric>> {
        Ok(self.metrics.lock().await.clone())
    }

    async fn record_metric(&self, metric: Metric) -> StorageResult<()> {
        self.metrics.lock().await.push(metric);
        Ok(())
    }

    async fn authenticate(&self, _username: &str, _password: &str) -> StorageResult<AuthToken> {
        Ok(AuthToken {
            token: "mock_token".to_string(),
            user_id: "mock_user".to_string(),
            expires_at: chrono::Utc::now() + chrono::Duration::hours(1),
            permissions: vec!["read".to_string(), "write".to_string()],
        })
    }

    async fn validate_token(&self, _token: &str) -> StorageResult<Option<AuthToken>> {
        Ok(None)
    }

    async fn subscribe_to_events(
        &self,
        _filters: Option<Vec<TaskEventType>>,
    ) -> StorageResult<BoxStream<'_, TaskEvent>> {
        use futures::stream;
        Ok(Box::pin(stream::empty()))
    }

    async fn publish_event(&self, _event: TaskEvent) -> StorageResult<()> {
        Ok(())
    }

    async fn search_tasks(
        &self,
        _name_pattern: Option<String>,
        status_filter: Option<Vec<TaskStatus>>,
        _created_after: Option<chrono::DateTime<chrono::Utc>>,
        _created_before: Option<chrono::DateTime<chrono::Utc>>,
        _limit: Option<usize>,
        _offset: Option<usize>,
    ) -> StorageResult<Vec<WorkflowTask>> {
        let tasks = self.tasks.lock().await;
        match status_filter {
            Some(statuses) => Ok(tasks
                .iter()
                .filter(|task| statuses.contains(&task.status))
                .cloned()
                .collect()),
            None => Ok(tasks.clone()),
        }
    }

    async fn get_task_dependencies(&self, _task_id: &str) -> StorageResult<Vec<String>> {
        Ok(vec![])
    }

    async fn get_dependent_tasks(&self, _task_id: &str) -> StorageResult<Vec<String>> {
        Ok(vec![])
    }

    async fn create_backup(&self, backup_path: &str) -> StorageResult<()> {
        if !self.backup_supported {
            return Err(StorageError::BackendError(anyhow::anyhow!(
                "Backup not supported"
            )));
        }

        let tasks = self.tasks.lock().await;
        let backup_data = serde_json::to_string_pretty(&*tasks)?;
        std::fs::write(backup_path, backup_data).map_err(StorageError::IoError)?;
        Ok(())
    }

    async fn restore_from_backup(&self, backup_path: &str) -> StorageResult<()> {
        if !self.backup_supported {
            return Err(StorageError::BackendError(anyhow::anyhow!(
                "Backup not supported"
            )));
        }

        let backup_data = std::fs::read_to_string(backup_path).map_err(StorageError::IoError)?;
        let restored_tasks: Vec<WorkflowTask> = serde_json::from_str(&backup_data)?;
        *self.tasks.lock().await = restored_tasks;
        Ok(())
    }

    async fn get_statistics(&self) -> StorageResult<StorageStatistics> {
        let tasks = self.tasks.lock().await;
        let pending = tasks
            .iter()
            .filter(|t| t.status == TaskStatus::Pending)
            .count();
        let running = tasks
            .iter()
            .filter(|t| t.status == TaskStatus::Running)
            .count();
        let completed = tasks
            .iter()
            .filter(|t| t.status == TaskStatus::Completed)
            .count();
        let failed = tasks
            .iter()
            .filter(|t| t.status == TaskStatus::Failed)
            .count();

        Ok(StorageStatistics {
            total_tasks: tasks.len(),
            pending_tasks: pending,
            running_tasks: running,
            completed_tasks: completed,
            failed_tasks: failed,
            total_storage_size: 0,
            last_cleanup: None,
            uptime: chrono::Duration::seconds(3600),
        })
    }
}

/// Helper to create a mock storage config
fn mock_storage_config(storage_type: &str) -> StorageConfig {
    StorageConfig {
        storage_type: storage_type.to_string(),
        data_dir: "/tmp/mock".to_string(),
        ..Default::default()
    }
}

#[tokio::test]
async fn test_migration_config_validation() {
    let config = MigrationConfig {
        source_config: mock_storage_config("filesystem"),
        target_config: mock_storage_config("surrealdb-embedded"),
        options: MigrationOptions::default(),
    };

    let migrator = StorageMigrator::new(config);
    // Validation happens during migration execution
    assert_eq!(migrator.report.status, MigrationStatus::Pending);
}

#[tokio::test]
async fn test_migration_options_default() {
    let options = MigrationOptions::default();
    assert!(!options.dry_run);
    assert!(options.create_backup);
    assert!(options.verify_integrity);
    assert_eq!(options.batch_size, 100);
    assert_eq!(options.max_retries, 3);
    assert!(!options.continue_on_error);
}

#[tokio::test]
async fn test_mock_storage_basic_operations() {
    let storage = MockStorage::new();

    // Test initialization
    assert!(storage.init().await.is_ok());
    assert!(storage.health_check().await.unwrap());

    // Test task operations
    storage
        .add_test_task("test-1", "Test Task", TaskStatus::Pending)
        .await;
    assert_eq!(storage.task_count().await, 1);

    let task = storage.get_task("test-1").await.unwrap().unwrap();
    assert_eq!(task.name, "Test Task");
    assert_eq!(task.status, TaskStatus::Pending);

    // Test status update
    storage
        .update_task_status("test-1", TaskStatus::Completed)
        .await
        .unwrap();
    let updated_task = storage.get_task("test-1").await.unwrap().unwrap();
    assert_eq!(updated_task.status, TaskStatus::Completed);
}

#[tokio::test]
async fn test_mock_storage_failure_simulation() {
    let storage = MockStorage::new();

    // Normal operation
    assert!(storage.health_check().await.unwrap());

    // Simulate failure
    storage.set_should_fail(true).await;
    assert!(!storage.health_check().await.unwrap());
    assert!(storage.init().await.is_err());
}

#[tokio::test]
async fn test_mock_storage_backup_operations() {
    let temp_dir = TempDir::new().unwrap();
    let backup_path = temp_dir.path().join("backup.json");
    let backup_path_str = backup_path.to_str().unwrap();

    let storage = MockStorage::new();

    // Add test data
    storage
        .add_test_task("test-1", "Task 1", TaskStatus::Pending)
        .await;
    storage
        .add_test_task("test-2", "Task 2", TaskStatus::Completed)
        .await;
    assert_eq!(storage.task_count().await, 2);

    // Test backup creation
    storage.create_backup(backup_path_str).await.unwrap();
    assert!(backup_path.exists());

    // Clear storage and restore
    {
        let mut tasks = storage.tasks.lock().await;
        tasks.clear();
    }
    assert_eq!(storage.task_count().await, 0);

    // Test restoration
    storage.restore_from_backup(backup_path_str).await.unwrap();
    assert_eq!(storage.task_count().await, 2);

    let task1 = storage.get_task("test-1").await.unwrap().unwrap();
    assert_eq!(task1.name, "Task 1");
}

#[tokio::test]
async fn test_mock_storage_no_backup_support() {
    let storage = MockStorage::new().with_backup_support(false);

    let result = storage.create_backup("/tmp/test.json").await;
    assert!(result.is_err());
    match result.unwrap_err() {
        StorageError::BackendError(_) => (),
        _ => panic!("Expected BackendError for unsupported backup"),
    }
}

#[tokio::test]
async fn test_migration_statistics_calculation() {
    let mut stats = MigrationStatistics::default();
    assert_eq!(stats.total_tasks_found, 0);
    assert_eq!(stats.tasks_migrated, 0);
    assert_eq!(stats.throughput_tasks_per_second, 0.0);

    stats.total_tasks_found = 100;
    stats.tasks_migrated = 95;
    stats.tasks_failed = 5;

    assert_eq!(stats.tasks_migrated, 95);
    assert_eq!(stats.tasks_failed, 5);
}

#[tokio::test]
async fn test_migration_error_tracking() {
    let config = MigrationConfig {
        source_config: mock_storage_config("filesystem"),
        target_config: mock_storage_config("surrealdb-embedded"),
        options: MigrationOptions::default(),
    };

    let mut migrator = StorageMigrator::new(config);

    // Add test error
    migrator.add_error(
        "test_error",
        "This is a test error",
        Some("Test context".to_string()),
        Some("task-123".to_string()),
    );

    assert_eq!(migrator.report.errors.len(), 1);
    let error = &migrator.report.errors[0];
    assert_eq!(error.error_type, "test_error");
    assert_eq!(error.message, "This is a test error");
    assert_eq!(error.context, Some("Test context".to_string()));
    assert_eq!(error.task_id, Some("task-123".to_string()));
}

#[tokio::test]
async fn test_migration_progress_tracking() {
    let config = MigrationConfig {
        source_config: mock_storage_config("filesystem"),
        target_config: mock_storage_config("surrealdb-embedded"),
        options: MigrationOptions::default(),
    };

    let progress_updates = Arc::new(Mutex::new(Vec::new()));
    let progress_capture = Arc::clone(&progress_updates);

    let progress_callback: ProgressCallback = {
        let capture = Arc::clone(&progress_capture);
        Arc::new(move |progress| {
            let capture = Arc::clone(&capture);
            let progress = progress.clone();
            tokio::spawn(async move {
                capture.lock().await.push(progress);
            });
        })
    };

    let migrator = StorageMigrator::new(config).with_progress_callback(progress_callback);

    migrator.update_progress("test_phase", 50, 100, "Testing progress");

    // Give the async callback time to execute
    tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;

    let updates = progress_updates.lock().await;
    assert_eq!(updates.len(), 1);
    assert_eq!(updates[0].phase, "test_phase");
    assert_eq!(updates[0].percentage, 50.0);
    assert_eq!(updates[0].message, "Testing progress");
}

#[tokio::test]
async fn test_rollback_config_creation() {
    let config = RollbackConfig {
        backup_path: "/tmp/backup.json".to_string(),
        migration_id: "migration-123".to_string(),
        rollback_strategy: RollbackStrategy::RestoreFromBackup,
    };

    let manager = RollbackManager::new(config);
    assert_eq!(manager.report.migration_id, "migration-123");
    assert_eq!(manager.report.status, RollbackStatus::Pending);
    assert!(matches!(
        manager.report.strategy_used,
        RollbackStrategy::RestoreFromBackup
    ));
}

#[tokio::test]
async fn test_rollback_action_tracking() {
    let config = RollbackConfig {
        backup_path: "/tmp/backup.json".to_string(),
        migration_id: "migration-123".to_string(),
        rollback_strategy: RollbackStrategy::Manual,
    };

    let mut manager = RollbackManager::new(config);

    manager.add_action("test_action", true, "This is a test action");

    assert_eq!(manager.report.actions_performed.len(), 1);
    let action = &manager.report.actions_performed[0];
    assert_eq!(action.action, "test_action");
    assert!(action.success);
    assert_eq!(action.details, "This is a test action");
}

#[tokio::test]
async fn test_rollback_manual_instructions() {
    let temp_dir = TempDir::new().unwrap();
    let backup_path = temp_dir.path().join("backup.json");

    let config = RollbackConfig {
        backup_path: backup_path.to_string_lossy().to_string(),
        migration_id: "migration-123".to_string(),
        rollback_strategy: RollbackStrategy::Manual,
    };

    let manager = RollbackManager::new(config);
    let storage = Arc::new(MockStorage::new()) as Arc<dyn TaskStorage>;

    let report = manager.execute_rollback(&storage).await.unwrap();

    assert!(matches!(report.status, RollbackStatus::Completed));
    assert_eq!(report.migration_id, "migration-123");
    assert!(!report.actions_performed.is_empty());

    // Check if manual instructions action was recorded
    let has_manual_action = report
        .actions_performed
        .iter()
        .any(|action| action.action == "manual_instructions_start");
    assert!(has_manual_action);
}

#[tokio::test]
async fn test_filesystem_storage_integration() {
    let temp_dir = TempDir::new().unwrap();
    let storage_path = temp_dir.path().to_path_buf();

    let storage = FilesystemStorage::new(storage_path).await.unwrap();
    let boxed_storage = Box::new(storage) as Box<dyn TaskStorage>;

    // Test basic storage operations
    assert!(boxed_storage.init().await.is_ok());
    assert!(boxed_storage.health_check().await.unwrap());

    // Add a test task
    let test_task = WorkflowTask {
        id: "fs-test-1".to_string(),
        name: "Filesystem Test Task".to_string(),
        command: "echo".to_string(),
        args: vec!["test".to_string()],
        dependencies: vec![],
        status: TaskStatus::Pending,
        created_at: chrono::Utc::now(),
        started_at: None,
        completed_at: None,
        output: None,
        error: None,
    };

    boxed_storage.enqueue(test_task, 1).await.unwrap();

    let retrieved_task = boxed_storage.get_task("fs-test-1").await.unwrap().unwrap();
    assert_eq!(retrieved_task.name, "Filesystem Test Task");
    assert_eq!(retrieved_task.status, TaskStatus::Pending);

    let all_tasks = boxed_storage.list_tasks(None).await.unwrap();
    assert_eq!(all_tasks.len(), 1);
}

#[tokio::test]
async fn test_migration_utility_functions() {
    // Test filesystem to embedded migration factory
    let migrator = StorageMigrator::filesystem_to_embedded(
        "/tmp/source",
        "/tmp/target",
        Some(MigrationOptions {
            dry_run: true,
            ..Default::default()
        }),
    );

    assert_eq!(migrator.config.source_config.storage_type, "filesystem");
    assert_eq!(
        migrator.config.target_config.storage_type,
        "surrealdb-embedded"
    );
    assert!(migrator.config.options.dry_run);

    // Test embedded to server migration factory
    let migrator = StorageMigrator::embedded_to_server(
        "/tmp/embedded",
        "ws://localhost:8000",
        "admin",
        "password",
        None,
    );

    assert_eq!(
        migrator.config.source_config.storage_type,
        "surrealdb-embedded"
    );
    assert_eq!(
        migrator.config.target_config.storage_type,
        "surrealdb-server"
    );
    assert_eq!(
        migrator.config.target_config.surrealdb_url,
        Some("ws://localhost:8000".to_string())
    );
    assert_eq!(
        migrator.config.target_config.surrealdb_username,
        Some("admin".to_string())
    );
}

#[tokio::test]
async fn test_time_range_functionality() {
    let now = chrono::Utc::now();
    let time_range = TimeRange::last_hours(24);
    let yesterday = now - chrono::Duration::hours(12);
    let tomorrow = now + chrono::Duration::hours(25); // Outside range

    assert!(time_range.contains(yesterday));
    // Check time range bounds more carefully due to timing precision
    assert!(time_range.start <= now && now <= time_range.end);
    assert!(!time_range.contains(tomorrow));

    let days_range = TimeRange::last_days(7);
    let last_week = now - chrono::Duration::days(3);
    let last_month = now - chrono::Duration::days(30);

    assert!(days_range.contains(last_week));
    assert!(!days_range.contains(last_month));
}

#[tokio::test]
async fn test_audit_entry_creation() {
    let entry = AuditEntry::status_change(
        "task-123".to_string(),
        Some(TaskStatus::Pending),
        TaskStatus::Running,
        Some("user-456".to_string()),
    );

    assert_eq!(entry.task_id, "task-123");
    assert_eq!(entry.operation, "status_change");
    assert_eq!(entry.old_status, Some(TaskStatus::Pending));
    assert_eq!(entry.new_status, Some(TaskStatus::Running));
    assert_eq!(entry.user_id, Some("user-456".to_string()));

    let create_entry = AuditEntry::task_created("task-789".to_string(), None);

    assert_eq!(create_entry.task_id, "task-789");
    assert_eq!(create_entry.operation, "created");
    assert_eq!(create_entry.old_status, None);
    assert_eq!(create_entry.new_status, Some(TaskStatus::Pending));
    assert_eq!(create_entry.user_id, None);
}

#[tokio::test]
async fn test_metric_creation() {
    let counter = Metric::counter("task.processed", 42.0);
    assert_eq!(counter.name, "task.processed");
    assert_eq!(counter.value, 42.0);
    assert!(counter.tags.is_empty());

    let gauge = Metric::gauge("queue.size", 15.0)
        .with_tag("status", "pending")
        .with_tag("priority", "high");

    assert_eq!(gauge.name, "queue.size");
    assert_eq!(gauge.value, 15.0);
    assert_eq!(gauge.tags.len(), 2);
    assert_eq!(gauge.tags.get("status"), Some(&"pending".to_string()));
    assert_eq!(gauge.tags.get("priority"), Some(&"high".to_string()));
}

#[tokio::test]
async fn test_auth_token_functionality() {
    let token = AuthToken {
        token: "test_token_123".to_string(),
        user_id: "user_456".to_string(),
        expires_at: chrono::Utc::now() + chrono::Duration::hours(1),
        permissions: vec!["read".to_string(), "write".to_string(), "admin".to_string()],
    };

    assert!(!token.is_expired());
    assert!(token.has_permission("read"));
    assert!(token.has_permission("write"));
    assert!(token.has_permission("admin"));
    assert!(!token.has_permission("delete"));

    let expired_token = AuthToken {
        token: "expired_token".to_string(),
        user_id: "user_789".to_string(),
        expires_at: chrono::Utc::now() - chrono::Duration::hours(1),
        permissions: vec![],
    };

    assert!(expired_token.is_expired());
    assert!(!expired_token.has_permission("read"));
}
