use std::env;
use std::path::Path;

use platform_config::ConfigLoader;
use serde::{Deserialize, Serialize};

use crate::error::{RegistryError, Result};

/// Main configuration
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Config {
    #[serde(default)]
    pub server: ServerConfig,

    // Old single-instance format (for backward compatibility)
    pub gitea: Option<GiteaConfig>,
    pub oci: Option<OciConfig>,

    // New multi-instance format
    #[serde(default)]
    pub sources: SourcesConfig,
    #[serde(default)]
    pub distributions: DistributionsConfig,

    #[serde(default)]
    pub cache: CacheConfig,
}

impl Config {
    /// Load configuration from Nickel (extension-registry.ncl) with fallback to
    /// hierarchy
    pub fn load() -> Result<Self> {
        let mut config = Self::load_from_nickel()
            .or_else(|_| Self::load_from_hierarchy())
            .unwrap_or_default();

        Self::migrate_to_multiinstance(&mut config);
        Self::apply_env_overrides_internal(&mut config);
        Ok(config)
    }

    /// Load configuration from Nickel extension-registry.ncl
    fn load_from_nickel() -> Result<Self> {
        let config_json = platform_config::load_service_config_from_ncl("extension-registry")
            .map_err(|e| RegistryError::Config(format!("Failed to load from Nickel: {}", e)))?;

        serde_json::from_value(config_json).map_err(RegistryError::Json)
    }

    /// Load configuration from hierarchical sources with mode support
    ///
    /// Priority order:
    /// 1. REGISTRY_CONFIG environment variable (explicit path)
    /// 2. REGISTRY_MODE environment variable (mode-specific file)
    /// 3. Hardcoded default
    ///
    /// After loading, applies environment variable overrides and migrates
    /// old single-instance configs to multi-instance format.
    pub fn load_from_hierarchy() -> Result<Self> {
        let mut config = if let Ok(path) = env::var("REGISTRY_CONFIG") {
            // Explicit config path provided
            load_config(&path)?
        } else if let Ok(mode) = env::var("REGISTRY_MODE") {
            // Mode-specific config file
            let path = format!(
                "provisioning/platform/config/extension-registry.{}.toml",
                mode
            );
            load_config(&path)?
        } else {
            // Fall back to default
            Self::default()
        };

        // Migrate old single-instance configs to new multi-instance format if needed
        Self::migrate_to_multiinstance(&mut config);

        Self::apply_env_overrides_internal(&mut config);
        Ok(config)
    }

    /// Migrate old single-instance configs to new multi-instance format
    ///
    /// Detects old-style Option-based configs and automatically moves them
    /// to the new Vec-based multi-instance format for seamless backward
    /// compatibility.
    fn migrate_to_multiinstance(config: &mut Self) {
        let mut migrated = false;

        // Migrate single Gitea instance to new format
        if let Some(gitea) = config.gitea.take() {
            if config.sources.gitea.is_empty() {
                config.sources.gitea.push(gitea);
                migrated = true;
            }
        }

        // Migrate single OCI instance to new format
        if let Some(oci) = config.oci.take() {
            if config.distributions.oci.is_empty() {
                config.distributions.oci.push(oci);
                migrated = true;
            }
        }

        if migrated {
            tracing::info!("Auto-migrated single-instance config to multi-instance format");
        }
    }

    /// Apply environment variable overrides to configuration (internal helper)
    ///
    /// Overrides take precedence over loaded config values.
    /// Pattern: REGISTRY_{SECTION}_{KEY}
    fn apply_env_overrides_internal(config: &mut Self) {
        if let Ok(val) = env::var("REGISTRY_SERVER_HOST") {
            config.server.host = val;
        }
        if let Ok(val) = env::var("REGISTRY_SERVER_PORT") {
            if let Ok(port) = val.parse() {
                config.server.port = port;
            }
        }
        if let Ok(val) = env::var("REGISTRY_SERVER_WORKERS") {
            if let Ok(workers) = val.parse() {
                config.server.workers = workers;
            }
        }
        if let Ok(val) = env::var("REGISTRY_GITEA_URL") {
            if let Some(ref mut gitea) = config.gitea {
                gitea.url = val;
            }
        }
        if let Ok(val) = env::var("REGISTRY_GITEA_ORG") {
            if let Some(ref mut gitea) = config.gitea {
                gitea.organization = val;
            }
        }
        if let Ok(val) = env::var("REGISTRY_OCI_REGISTRY") {
            if let Some(ref mut oci) = config.oci {
                oci.registry = val;
            }
        }
        if let Ok(val) = env::var("REGISTRY_OCI_NAMESPACE") {
            if let Some(ref mut oci) = config.oci {
                oci.namespace = val;
            }
        }
        if let Ok(val) = env::var("REGISTRY_CACHE_CAPACITY") {
            if let Ok(capacity) = val.parse() {
                config.cache.capacity = capacity;
            }
        }
        if let Ok(val) = env::var("REGISTRY_CACHE_TTL") {
            if let Ok(ttl) = val.parse() {
                config.cache.ttl_seconds = ttl;
            }
        }
    }

    /// Validate configuration
    pub fn validate(&self) -> Result<()> {
        // Check if at least one backend is configured (old or new format)
        let has_old_backends = self.gitea.is_some() || self.oci.is_some();
        let has_new_source_backends = !self.sources.gitea.is_empty()
            || !self.sources.forgejo.is_empty()
            || !self.sources.github.is_empty();
        let has_new_dist_backends = !self.distributions.oci.is_empty();

        if !has_old_backends && !has_new_source_backends && !has_new_dist_backends {
            return Err(RegistryError::Config(
                "At least one backend must be configured (gitea, forgejo, github, or oci)"
                    .to_string(),
            ));
        }

        // Validate old single-instance configs if present
        if let Some(ref gitea) = self.gitea {
            gitea.validate()?;
        }

        if let Some(ref oci) = self.oci {
            oci.validate()?;
        }

        // Validate new multi-instance configs
        for gitea_config in &self.sources.gitea {
            gitea_config.validate()?;
        }

        for forgejo_config in &self.sources.forgejo {
            forgejo_config.validate()?;
        }

        for github_config in &self.sources.github {
            github_config.validate()?;
        }

        for oci_config in &self.distributions.oci {
            oci_config.validate()?;
        }

        self.cache.validate()?;

        Ok(())
    }
}

impl ConfigLoader for Config {
    fn service_name() -> &'static str {
        "extension-registry"
    }

    fn load_from_hierarchy() -> std::result::Result<Self, Box<dyn std::error::Error + Send + Sync>>
    {
        let service = Self::service_name();

        if let Some(path) = platform_config::resolve_config_path(service) {
            return Self::from_path(&path).map_err(|e| {
                Box::new(std::io::Error::other(e.to_string()))
                    as Box<dyn std::error::Error + Send + Sync>
            });
        }

        // Fallback to defaults
        Ok(Self::default())
    }

    fn apply_env_overrides(
        &mut self,
    ) -> std::result::Result<(), Box<dyn std::error::Error + Send + Sync>> {
        Self::apply_env_overrides_internal(self);
        Ok(())
    }

    fn from_path<P: AsRef<Path>>(
        path: P,
    ) -> std::result::Result<Self, Box<dyn std::error::Error + Send + Sync>> {
        let path = path.as_ref();
        let json_value = platform_config::format::load_config(path).map_err(|e| {
            let err: Box<dyn std::error::Error + Send + Sync> = Box::new(e);
            err
        })?;

        serde_json::from_value(json_value).map_err(|e| {
            let err_msg = format!(
                "Failed to deserialize extension-registry config from {:?}: {}",
                path, e
            );
            Box::new(std::io::Error::new(
                std::io::ErrorKind::InvalidData,
                err_msg,
            )) as Box<dyn std::error::Error + Send + Sync>
        })
    }
}

/// Server configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerConfig {
    #[serde(default = "default_host")]
    pub host: String,
    #[serde(default = "default_port")]
    pub port: u16,
    #[serde(default = "default_workers")]
    pub workers: usize,
    #[serde(default)]
    pub enable_cors: bool,
    #[serde(default)]
    pub enable_compression: bool,
}

impl Default for ServerConfig {
    fn default() -> Self {
        Self {
            host: default_host(),
            port: default_port(),
            workers: default_workers(),
            enable_cors: false,
            enable_compression: true,
        }
    }
}

/// Multi-instance source backends configuration
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct SourcesConfig {
    #[serde(default)]
    pub gitea: Vec<GiteaConfig>,
    #[serde(default)]
    pub forgejo: Vec<GiteaConfig>,
    #[serde(default)]
    pub github: Vec<GiteaConfig>,
}

/// Multi-instance distribution backends configuration
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct DistributionsConfig {
    #[serde(default)]
    pub oci: Vec<OciConfig>,
}

fn default_host() -> String {
    "0.0.0.0".to_string()
}

fn default_port() -> u16 {
    8082
}

fn default_workers() -> usize {
    4
}

/// Gitea configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GiteaConfig {
    #[serde(default)]
    pub id: Option<String>,
    pub url: String,
    pub organization: String,
    pub token_path: String,
    #[serde(default = "default_timeout")]
    pub timeout_seconds: u64,
    #[serde(default)]
    pub verify_ssl: bool,
}

impl GiteaConfig {
    fn validate(&self) -> Result<()> {
        if self.url.is_empty() {
            return Err(RegistryError::Config(
                "Gitea URL cannot be empty".to_string(),
            ));
        }

        if self.organization.is_empty() {
            return Err(RegistryError::Config(
                "Gitea organization cannot be empty".to_string(),
            ));
        }

        // vault:// references are resolved at runtime; file check only for filesystem
        // paths
        if !self.token_path.starts_with("vault://") && !Path::new(&self.token_path).exists() {
            return Err(RegistryError::Config(format!(
                "Gitea token file not found: {}",
                self.token_path
            )));
        }

        Ok(())
    }

    /// Read token from file. Only call for non-vault:// paths.
    pub fn read_token(&self) -> Result<String> {
        if self.token_path.starts_with("vault://") {
            return Err(RegistryError::Config(
                "token_path is a vault:// reference — use ClientFactory::create_from_config_async \
                 to resolve"
                    .to_string(),
            ));
        }
        std::fs::read_to_string(&self.token_path)
            .map(|s| s.trim().to_string())
            .map_err(|e| RegistryError::Config(format!("Failed to read Gitea token: {}", e)))
    }
}

/// OCI registry configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OciConfig {
    #[serde(default)]
    pub id: Option<String>,
    pub registry: String,
    pub namespace: String,
    pub auth_token_path: Option<String>,
    #[serde(default = "default_timeout")]
    pub timeout_seconds: u64,
    #[serde(default)]
    pub verify_ssl: bool,
}

impl OciConfig {
    fn validate(&self) -> Result<()> {
        if self.registry.is_empty() {
            return Err(RegistryError::Config(
                "OCI registry cannot be empty".to_string(),
            ));
        }

        if self.namespace.is_empty() {
            return Err(RegistryError::Config(
                "OCI namespace cannot be empty".to_string(),
            ));
        }

        if let Some(ref token_path) = self.auth_token_path {
            // vault:// references are resolved at runtime; file check only for filesystem
            // paths
            if !token_path.starts_with("vault://") && !Path::new(token_path).exists() {
                return Err(RegistryError::Config(format!(
                    "OCI token file not found: {}",
                    token_path
                )));
            }
        }

        Ok(())
    }

    /// Read auth token from file. Only call for non-vault:// paths.
    pub fn read_token(&self) -> Result<Option<String>> {
        if let Some(ref path) = self.auth_token_path {
            if path.starts_with("vault://") {
                return Err(RegistryError::Config(
                    "auth_token_path is a vault:// reference — use \
                     ClientFactory::create_from_config_async to resolve"
                        .to_string(),
                ));
            }
            std::fs::read_to_string(path)
                .map(|s| Some(s.trim().to_string()))
                .map_err(|e| RegistryError::Config(format!("Failed to read OCI token: {}", e)))
        } else {
            Ok(None)
        }
    }
}

fn default_timeout() -> u64 {
    30
}

/// Cache configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CacheConfig {
    #[serde(default = "default_capacity")]
    pub capacity: usize,
    #[serde(default = "default_ttl")]
    pub ttl_seconds: u64,
    #[serde(default)]
    pub enable_metadata_cache: bool,
    #[serde(default)]
    pub enable_list_cache: bool,
}

impl Default for CacheConfig {
    fn default() -> Self {
        Self {
            capacity: default_capacity(),
            ttl_seconds: default_ttl(),
            enable_metadata_cache: true,
            enable_list_cache: true,
        }
    }
}

impl CacheConfig {
    fn validate(&self) -> Result<()> {
        if self.capacity == 0 {
            return Err(RegistryError::Config(
                "Cache capacity cannot be zero".to_string(),
            ));
        }

        if self.ttl_seconds == 0 {
            return Err(RegistryError::Config(
                "Cache TTL cannot be zero".to_string(),
            ));
        }

        Ok(())
    }
}

fn default_capacity() -> usize {
    1000
}

fn default_ttl() -> u64 {
    300 // 5 minutes
}

/// Load configuration from file
pub fn load_config(path: &str) -> Result<Config> {
    let contents = std::fs::read_to_string(path).map_err(RegistryError::Io)?;

    let config: Config = toml::from_str(&contents)
        .map_err(|e| RegistryError::Config(format!("Failed to parse config: {}", e)))?;

    config.validate()?;

    Ok(config)
}

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

    #[test]
    fn test_default_configs() {
        let server = ServerConfig::default();
        assert_eq!(server.host, "0.0.0.0");
        assert_eq!(server.port, 8082);

        let cache = CacheConfig::default();
        assert_eq!(cache.capacity, 1000);
        assert_eq!(cache.ttl_seconds, 300);
    }
}
