use std::collections::{HashMap, HashSet};

use serde_json::Value;

use crate::error::{ConfigError, Result};
use crate::git::GitRepoCache;

/// Platform startup manager
///
/// Handles platform initialization, service enablement checks,
/// and dependency validation during startup.
pub struct PlatformStartup {
    git_schemas: GitRepoCache,
    git_configs: GitRepoCache,
    enabled_services: HashSet<String>,
    service_dependencies: HashMap<String, Vec<String>>,
}

impl PlatformStartup {
    /// Initialize platform startup manager from deployment-mode config
    pub fn new(deployment_config: &Value) -> Result<Self> {
        // Extract enabled services from deployment-mode.ncl
        let enabled_services = extract_enabled_services(deployment_config)?;

        // Extract Git repository configuration
        let (git_schemas, git_configs) = extract_git_repos(deployment_config)?;

        // Extract service dependencies
        let service_dependencies = extract_service_dependencies(deployment_config)?;

        Ok(Self {
            git_schemas,
            git_configs,
            enabled_services,
            service_dependencies,
        })
    }

    /// Check if a service is enabled in deployment-mode
    pub fn is_service_enabled(&self, service_name: &str) -> bool {
        self.enabled_services.contains(service_name)
    }

    /// Get list of enabled services
    pub fn enabled_services(&self) -> Vec<String> {
        self.enabled_services.iter().cloned().collect()
    }

    /// Validate that all dependencies for a service are enabled
    pub fn validate_dependencies(&self, service_name: &str) -> Result<()> {
        let dependencies = self
            .service_dependencies
            .get(service_name)
            .cloned()
            .unwrap_or_default();

        for dep in dependencies {
            if !self.is_service_enabled(&dep) {
                return Err(ConfigError::validation_failed(format!(
                    "Service '{}' requires '{}' to be enabled",
                    service_name, dep
                )));
            }
        }

        Ok(())
    }

    /// Ensure Git repositories are cloned/updated and return cache paths
    /// If cloning fails, attempt to use existing cache
    pub fn setup_git_repos(&self) -> Result<(std::path::PathBuf, std::path::PathBuf)> {
        let schemas_path = match self.git_schemas.ensure_cached() {
            Ok(path) => {
                tracing::info!("✓ Schemas repository ready");
                path
            }
            Err(e) => {
                tracing::warn!(
                    "Failed to clone schemas repository: {}. Attempting to use existing cache.",
                    e
                );
                // Try to use existing cache directory
                let cache_dir = self.git_schemas.cache_dir.clone();
                if cache_dir.exists() {
                    tracing::info!("Using existing schemas cache at {:?}", cache_dir);
                    cache_dir
                } else {
                    return Err(e);
                }
            }
        };

        let configs_path = match self.git_configs.ensure_cached() {
            Ok(path) => {
                tracing::info!("✓ Configs repository ready");
                path
            }
            Err(e) => {
                tracing::warn!(
                    "Failed to clone configs repository: {}. Attempting to use existing cache.",
                    e
                );
                // Try to use existing cache directory
                let cache_dir = self.git_configs.cache_dir.clone();
                if cache_dir.exists() {
                    tracing::info!("Using existing configs cache at {:?}", cache_dir);
                    cache_dir
                } else {
                    return Err(e);
                }
            }
        };

        tracing::info!("✓ Git repositories ready (using cache)");
        tracing::debug!("  Schemas cache: {:?}", schemas_path);
        tracing::debug!("  Configs cache: {:?}", configs_path);

        Ok((schemas_path, configs_path))
    }

    /// Get service startup order based on dependencies
    pub fn get_startup_order(&self) -> Result<Vec<String>> {
        let mut order = Vec::new();
        let mut visited = HashSet::new();

        for service in &self.enabled_services {
            self.visit_service(service, &mut visited, &mut order)?;
        }

        Ok(order)
    }

    fn visit_service(
        &self,
        service: &str,
        visited: &mut HashSet<String>,
        order: &mut Vec<String>,
    ) -> Result<()> {
        if visited.contains(service) {
            return Ok(());
        }

        // Visit dependencies first
        if let Some(deps) = self.service_dependencies.get(service) {
            for dep in deps {
                self.visit_service(dep, visited, order)?;
            }
        }

        visited.insert(service.to_string());
        order.push(service.to_string());

        Ok(())
    }
}

/// Extract enabled services from deployment-mode config
///
/// Supports two formats:
/// 1. Legacy: "enabled_services": ["service1", "service2"]
/// 2. Current: "services": { "service1": { "enabled": true }, "service2": {
///    "enabled": false } }
fn extract_enabled_services(config: &Value) -> Result<HashSet<String>> {
    // Try current format first: services object with enabled field
    if let Some(services_obj) = config.get("services").and_then(|v| v.as_object()) {
        let enabled: HashSet<String> = services_obj
            .iter()
            .filter_map(|(name, cfg)| {
                cfg.get("enabled")
                    .and_then(|v| v.as_bool())
                    .filter(|&enabled| enabled)
                    .map(|_| name.clone())
            })
            .collect();

        if !enabled.is_empty() {
            return Ok(enabled);
        }
    }

    // Fall back to legacy format: enabled_services array
    if let Some(services_array) = config.get("enabled_services").and_then(|v| v.as_array()) {
        let enabled: HashSet<String> = services_array
            .iter()
            .filter_map(|v| v.as_str().map(|s| s.to_string()))
            .collect();

        if !enabled.is_empty() {
            return Ok(enabled);
        }
    }

    Err(ConfigError::validation_failed(
        "No enabled services found in deployment-mode.ncl - check services object or \
         enabled_services array",
    ))
}

/// Extract Git repository configurations from deployment-mode config
fn extract_git_repos(config: &Value) -> Result<(GitRepoCache, GitRepoCache)> {
    let git_config = config
        .get("git")
        .ok_or_else(|| ConfigError::validation_failed("git config not found in deployment-mode"))?;

    let schemas_config = git_config
        .get("schemas_repo")
        .ok_or_else(|| ConfigError::validation_failed("schemas_repo config not found"))?;

    let configs_config = git_config
        .get("configs_repo")
        .ok_or_else(|| ConfigError::validation_failed("configs_repo config not found"))?;

    let schemas_repo = GitRepoCache {
        url: extract_string(schemas_config, "url")?,
        branch: extract_string(schemas_config, "branch").unwrap_or_else(|_| "main".to_string()),
        cache_dir: extract_string(schemas_config, "cache_dir")?.into(),
        update_check: schemas_config
            .get("update_check")
            .and_then(|v| v.as_bool())
            .unwrap_or(true),
    };

    let configs_repo = GitRepoCache {
        url: extract_string(configs_config, "url")?,
        branch: extract_string(configs_config, "branch").unwrap_or_else(|_| "main".to_string()),
        cache_dir: extract_string(configs_config, "cache_dir")?.into(),
        update_check: configs_config
            .get("update_check")
            .and_then(|v| v.as_bool())
            .unwrap_or(true),
    };

    Ok((schemas_repo, configs_repo))
}

/// Extract service dependencies from deployment-mode config
fn extract_service_dependencies(config: &Value) -> Result<HashMap<String, Vec<String>>> {
    let mut dependencies = HashMap::new();

    if let Some(services) = config.get("services").and_then(|v| v.as_object()) {
        for (service_name, service_config) in services {
            if let Some(deps_array) = service_config
                .get("dependencies")
                .and_then(|v| v.as_array())
            {
                let deps: Vec<String> = deps_array
                    .iter()
                    .filter_map(|v| v.as_str().map(|s| s.to_string()))
                    .collect();

                dependencies.insert(service_name.clone(), deps);
            }
        }
    }

    Ok(dependencies)
}

/// Helper to extract string value from JSON
fn extract_string(value: &Value, key: &str) -> Result<String> {
    value
        .get(key)
        .and_then(|v| v.as_str())
        .map(|s| s.to_string())
        .ok_or_else(|| {
            ConfigError::validation_failed(format!("'{}' not found or not a string", key))
        })
}

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

    fn create_test_config() -> Value {
        serde_json::json!({
            "enabled_services": ["orchestrator", "vault_service"],
            "services": {
                "orchestrator": {
                    "dependencies": ["vault_service"]
                },
                "vault_service": {
                    "dependencies": []
                }
            },
            "git": {
                "schemas_repo": {
                    "url": "http://localhost:3000/provisioning/provisioning-schemas.git",
                    "branch": "main",
                    "cache_dir": "~/.cache/provisioning/schemas",
                    "update_check": true
                },
                "configs_repo": {
                    "url": "http://localhost:3000/provisioning/provisioning-configs.git",
                    "branch": "main",
                    "cache_dir": "~/.cache/provisioning/configs",
                    "update_check": true
                }
            }
        })
    }

    #[test]
    fn test_extract_enabled_services() {
        let config = create_test_config();
        let services = extract_enabled_services(&config).expect("Should extract services");

        assert!(services.contains("orchestrator"));
        assert!(services.contains("vault_service"));
        assert_eq!(services.len(), 2);
    }

    #[test]
    fn test_platform_startup_is_service_enabled() {
        let config = create_test_config();
        let startup = PlatformStartup::new(&config).expect("Should create startup");

        assert!(startup.is_service_enabled("orchestrator"));
        assert!(startup.is_service_enabled("vault_service"));
        assert!(!startup.is_service_enabled("mcp-server"));
    }

    #[test]
    fn test_validate_dependencies_success() {
        let config = create_test_config();
        let startup = PlatformStartup::new(&config).expect("Should create startup");

        let result = startup.validate_dependencies("orchestrator");
        assert!(result.is_ok(), "Should validate dependencies");
    }

    #[test]
    fn test_validate_dependencies_failure() {
        let mut config = create_test_config();
        // Remove vault_service from enabled services
        if let Some(services) = config
            .get_mut("enabled_services")
            .and_then(|v| v.as_array_mut())
        {
            services.retain(|v| v.as_str() != Some("vault_service"));
        }

        let startup = PlatformStartup::new(&config).expect("Should create startup");
        let result = startup.validate_dependencies("orchestrator");
        assert!(result.is_err(), "Should fail validation");
    }

    #[test]
    fn test_get_startup_order() {
        let config = create_test_config();
        let startup = PlatformStartup::new(&config).expect("Should create startup");

        let order = startup
            .get_startup_order()
            .expect("Should get startup order");
        // vault_service should come before orchestrator (has fewer dependencies)
        let vault_idx = order.iter().position(|s| s == "vault_service");
        let orch_idx = order.iter().position(|s| s == "orchestrator");

        assert!(vault_idx.is_some());
        assert!(orch_idx.is_some());
        assert!(vault_idx.unwrap() < orch_idx.unwrap());
    }
}
