//! Storage factory for creating storage backend instances
//!
//! This module provides a factory pattern implementation for creating
//! different storage backends based on configuration. It supports:
//! - Filesystem storage (always available)
//! - SurrealDB embedded storage (feature-gated)
//! - SurrealDB server storage (feature-gated)

use std::path::PathBuf;

use anyhow::{anyhow, Context, Result};
use serde::{Deserialize, Serialize};
use tracing::info;

use super::filesystem::FilesystemStorage;
#[cfg(feature = "surrealdb")]
use super::surrealdb::{StorageMode, SurrealStorage};
use super::traits::TaskStorage;

/// Storage factory configuration extracted from CLI arguments
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageConfig {
    pub storage_type: String,
    pub data_dir: String,
    // SurrealDB-specific configuration
    pub surrealdb_url: Option<String>,
    pub surrealdb_namespace: Option<String>,
    pub surrealdb_database: Option<String>,
    pub surrealdb_username: Option<String>,
    pub surrealdb_password: Option<String>,
}

impl Default for StorageConfig {
    fn default() -> Self {
        Self {
            storage_type: "filesystem".to_string(),
            data_dir: "./data".to_string(),
            surrealdb_url: None,
            surrealdb_namespace: Some("orchestrator".to_string()),
            surrealdb_database: Some("tasks".to_string()),
            surrealdb_username: None,
            surrealdb_password: None,
        }
    }
}

impl StorageConfig {
    /// Create configuration from CLI arguments struct
    pub fn from_args(args: &crate::Args) -> Self {
        Self {
            storage_type: args.storage_type.clone(),
            data_dir: args.data_dir.clone(),
            surrealdb_url: args.surrealdb_url.clone(),
            surrealdb_namespace: args.surrealdb_namespace.clone(),
            surrealdb_database: args.surrealdb_database.clone(),
            surrealdb_username: args.surrealdb_username.clone(),
            surrealdb_password: args.surrealdb_password.clone(),
        }
    }

    /// Validate configuration based on storage type
    pub fn validate(&self) -> Result<()> {
        match self.storage_type.as_str() {
            "filesystem" => {
                // Filesystem only needs data_dir
                if self.data_dir.is_empty() {
                    return Err(anyhow!("data_dir is required for filesystem storage"));
                }
                Ok(())
            }
            #[cfg(feature = "surrealdb")]
            "surrealdb-embedded" => {
                if self.data_dir.is_empty() {
                    return Err(anyhow!("data_dir is required for SurrealDB embedded mode"));
                }
                Ok(())
            }
            #[cfg(feature = "surrealdb")]
            "surrealdb-server" => {
                if self.surrealdb_url.is_none() {
                    return Err(anyhow!(
                        "surrealdb_url is required for SurrealDB server mode"
                    ));
                }
                if self.surrealdb_username.is_none() {
                    return Err(anyhow!(
                        "surrealdb_username is required for SurrealDB server mode"
                    ));
                }
                if self.surrealdb_password.is_none() {
                    return Err(anyhow!(
                        "surrealdb_password is required for SurrealDB server mode"
                    ));
                }
                Ok(())
            }
            #[cfg(not(feature = "surrealdb"))]
            storage_type if storage_type.starts_with("surrealdb") => Err(anyhow!(
                "SurrealDB storage types require the 'surrealdb' feature to be enabled. Compile \
                 with --features surrealdb to enable SurrealDB backends."
            )),
            _ => Err(anyhow!(
                "Unsupported storage type: '{}'. Supported types: filesystem{}",
                self.storage_type,
                if cfg!(feature = "surrealdb") {
                    ", surrealdb-embedded, surrealdb-server"
                } else {
                    ""
                }
            )),
        }
    }
}

/// Factory trait for creating storage instances
pub trait StorageFactory {
    /// Create a storage backend instance based on configuration
    fn create_storage(
        config: StorageConfig,
    ) -> impl std::future::Future<Output = Result<std::sync::Arc<dyn TaskStorage>>> + Send;
}

/// Default storage factory implementation
pub struct DefaultStorageFactory;

impl StorageFactory for DefaultStorageFactory {
    async fn create_storage(config: StorageConfig) -> Result<std::sync::Arc<dyn TaskStorage>> {
        // Validate configuration first
        config
            .validate()
            .context("Storage configuration validation failed")?;

        info!("Creating {} storage backend", config.storage_type);

        match config.storage_type.as_str() {
            "filesystem" => {
                let data_path = PathBuf::from(&config.data_dir);
                let storage = FilesystemStorage::new(data_path)
                    .await
                    .context("Failed to create filesystem storage")?;

                let arc_storage = std::sync::Arc::new(storage) as std::sync::Arc<dyn TaskStorage>;

                // Initialize the storage
                arc_storage
                    .init()
                    .await
                    .context("Failed to initialize filesystem storage")?;

                info!("Filesystem storage initialized at: {}", config.data_dir);
                Ok(arc_storage)
            }
            #[cfg(feature = "surrealdb")]
            "surrealdb-embedded" => {
                let storage_path = PathBuf::from(&config.data_dir).join("surrealdb");
                let storage_mode = StorageMode::embedded(&storage_path.to_string_lossy())
                    .with_namespace_db(
                        config
                            .surrealdb_namespace
                            .as_deref()
                            .unwrap_or("orchestrator"),
                        config.surrealdb_database.as_deref().unwrap_or("tasks"),
                    );

                let storage = SurrealStorage::new(super::surrealdb::SurrealConfig {
                    mode: storage_mode,
                    ..Default::default()
                });

                let arc_storage = std::sync::Arc::new(storage) as std::sync::Arc<dyn TaskStorage>;

                // Initialize the storage
                arc_storage
                    .init()
                    .await
                    .context("Failed to initialize SurrealDB embedded storage")?;

                info!(
                    "SurrealDB embedded storage initialized at: {}",
                    storage_path.display()
                );
                Ok(arc_storage)
            }
            #[cfg(feature = "surrealdb")]
            "surrealdb-server" => {
                let url = config
                    .surrealdb_url
                    .as_deref()
                    .ok_or_else(|| anyhow!("surrealdb_url is required"))?;
                let username = config
                    .surrealdb_username
                    .as_deref()
                    .ok_or_else(|| anyhow!("surrealdb_username is required"))?;
                let password = config
                    .surrealdb_password
                    .as_deref()
                    .ok_or_else(|| anyhow!("surrealdb_password is required"))?;

                let storage_mode = StorageMode::server(url, username, password).with_namespace_db(
                    config
                        .surrealdb_namespace
                        .as_deref()
                        .unwrap_or("orchestrator"),
                    config.surrealdb_database.as_deref().unwrap_or("tasks"),
                );

                let storage = SurrealStorage::new(super::surrealdb::SurrealConfig {
                    mode: storage_mode,
                    ..Default::default()
                });

                let arc_storage = std::sync::Arc::new(storage) as std::sync::Arc<dyn TaskStorage>;

                // Initialize the storage
                arc_storage
                    .init()
                    .await
                    .context("Failed to initialize SurrealDB server storage")?;

                info!("SurrealDB server storage initialized: {}", url);
                Ok(arc_storage)
            }
            #[cfg(not(feature = "surrealdb"))]
            storage_type if storage_type.starts_with("surrealdb") => Err(anyhow!(
                "SurrealDB storage type '{}' requires the 'surrealdb' feature. Please compile \
                 with --features surrealdb to enable SurrealDB backends.",
                storage_type
            )),
            _ => Err(anyhow!(
                "Unsupported storage type: '{}'. Available types: filesystem{}",
                config.storage_type,
                if cfg!(feature = "surrealdb") {
                    ", surrealdb-embedded, surrealdb-server"
                } else {
                    ""
                }
            )),
        }
    }
}

/// Convenience function to create storage using the default factory
pub async fn create_storage(config: StorageConfig) -> Result<std::sync::Arc<dyn TaskStorage>> {
    DefaultStorageFactory::create_storage(config).await
}

/// Convenience function to create storage from CLI args
pub async fn create_storage_from_args(
    args: &crate::Args,
) -> Result<std::sync::Arc<dyn TaskStorage>> {
    let config = StorageConfig::from_args(args);
    create_storage(config).await
}

/// Get list of available storage types based on compiled features
pub fn available_storage_types() -> Vec<String> {
    let types = vec!["filesystem".to_string()];

    #[cfg(feature = "surrealdb")]
    {
        let mut types = types;
        types.push("surrealdb-embedded".to_string());
        types.push("surrealdb-server".to_string());
        types
    }

    #[cfg(not(feature = "surrealdb"))]
    types
}

/// Get detailed help text for storage configuration
pub fn storage_help_text() -> String {
    let mut help = String::from("Available storage backends:\n\n");

    help.push_str("filesystem:\n");
    help.push_str("  Default file-based storage using JSON files\n");
    help.push_str("  Required: --data-dir <path>\n");
    help.push_str("  Example: --storage-type filesystem --data-dir ./data\n\n");

    #[cfg(feature = "surrealdb")]
    {
        help.push_str("surrealdb-embedded:\n");
        help.push_str("  Embedded SurrealDB using RocksDB engine\n");
        help.push_str("  Required: --data-dir <path>\n");
        help.push_str("  Optional: --surrealdb-namespace <ns> --surrealdb-database <db>\n");
        help.push_str("  Example: --storage-type surrealdb-embedded --data-dir ./data\n\n");

        help.push_str("surrealdb-server:\n");
        help.push_str("  Remote SurrealDB server via WebSocket\n");
        help.push_str(
            "  Required: --surrealdb-url <url> --surrealdb-username <user> --surrealdb-password \
             <pass>\n",
        );
        help.push_str("  Optional: --surrealdb-namespace <ns> --surrealdb-database <db>\n");
        help.push_str(
            "  Example: --storage-type surrealdb-server --surrealdb-url ws://localhost:8000\n",
        );
        help.push_str("           --surrealdb-username admin --surrealdb-password admin123\n\n");
    }

    #[cfg(not(feature = "surrealdb"))]
    {
        help.push_str("Note: SurrealDB backends require compilation with --features surrealdb\n");
    }

    help
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_storage_config_validation() {
        // Valid filesystem config
        let fs_config = StorageConfig {
            storage_type: "filesystem".to_string(),
            data_dir: "./test_data".to_string(),
            ..Default::default()
        };
        assert!(fs_config.validate().is_ok());

        // Invalid filesystem config (empty data_dir)
        let invalid_fs_config = StorageConfig {
            storage_type: "filesystem".to_string(),
            data_dir: "".to_string(),
            ..Default::default()
        };
        assert!(invalid_fs_config.validate().is_err());

        #[cfg(feature = "surrealdb")]
        {
            // Valid embedded config
            let embedded_config = StorageConfig {
                storage_type: "surrealdb-embedded".to_string(),
                data_dir: "./test_data".to_string(),
                ..Default::default()
            };
            assert!(embedded_config.validate().is_ok());

            // Valid server config
            let server_config = StorageConfig {
                storage_type: "surrealdb-server".to_string(),
                surrealdb_url: Some("ws://localhost:8000".to_string()),
                surrealdb_username: Some("admin".to_string()),
                surrealdb_password: Some("password".to_string()),
                ..Default::default()
            };
            assert!(server_config.validate().is_ok());

            // Invalid server config (missing URL)
            let invalid_server_config = StorageConfig {
                storage_type: "surrealdb-server".to_string(),
                surrealdb_username: Some("admin".to_string()),
                surrealdb_password: Some("password".to_string()),
                ..Default::default()
            };
            assert!(invalid_server_config.validate().is_err());
        }

        // Unknown storage type
        let unknown_config = StorageConfig {
            storage_type: "unknown".to_string(),
            ..Default::default()
        };
        assert!(unknown_config.validate().is_err());
    }

    #[test]
    fn test_available_storage_types() {
        let types = available_storage_types();
        assert!(types.contains(&"filesystem".to_string()));

        #[cfg(feature = "surrealdb")]
        {
            assert!(types.contains(&"surrealdb-embedded".to_string()));
            assert!(types.contains(&"surrealdb-server".to_string()));
            assert_eq!(types.len(), 3);
        }

        #[cfg(not(feature = "surrealdb"))]
        {
            assert_eq!(types.len(), 1);
        }
    }

    #[test]
    fn test_storage_help_text() {
        let help = storage_help_text();
        assert!(help.contains("filesystem"));

        #[cfg(feature = "surrealdb")]
        {
            assert!(help.contains("surrealdb-embedded"));
            assert!(help.contains("surrealdb-server"));
        }

        #[cfg(not(feature = "surrealdb"))]
        {
            assert!(help.contains("SurrealDB backends require compilation"));
        }
    }
}
