use std::env;

use anyhow::{anyhow, bail, Context};

use crate::types::{MicroserviceConfig, RuntimeConfig, RuntimeIdentity};

fn required_env(name: &str) -> anyhow::Result<String> {
    let value =
        env::var(name).with_context(|| format!("Missing required environment variable: {name}"))?;
    if value.is_empty() {
        bail!("Missing required environment variable: {name}");
    }
    Ok(value)
}

fn number_env<T>(name: &str) -> anyhow::Result<T>
where
    T: std::str::FromStr + PartialOrd + From<u8>,
    <T as std::str::FromStr>::Err: std::fmt::Display,
{
    let raw = required_env(name)?;
    let parsed: T = raw.parse().map_err(|error| {
        anyhow!("Environment variable {name} must be numeric, got {raw}: {error}")
    })?;
    if parsed <= T::from(0) {
        bail!("Environment variable {name} must be positive, got {raw}");
    }
    Ok(parsed)
}

fn optional_number_env<T>(name: &str, fallback: T) -> anyhow::Result<T>
where
    T: std::str::FromStr + PartialOrd + From<u8>,
    <T as std::str::FromStr>::Err: std::fmt::Display,
{
    match env::var(name) {
        Ok(raw) if !raw.is_empty() => {
            let parsed: T = raw.parse().map_err(|error| {
                anyhow!("Environment variable {name} must be numeric, got {raw}: {error}")
            })?;
            if parsed <= T::from(0) {
                bail!("Environment variable {name} must be positive, got {raw}");
            }
            Ok(parsed)
        }
        _ => Ok(fallback),
    }
}

fn read_microservices_env() -> anyhow::Result<Vec<MicroserviceConfig>> {
    let raw = env::var("MICROSERVICES_JSON").unwrap_or_default();
    if raw.is_empty() {
        return Ok(Vec::new());
    }
    let mut services: Vec<MicroserviceConfig> = serde_json::from_str(&raw)
        .context("MICROSERVICES_JSON must be a valid microservice array")?;
    for (index, service) in services.iter_mut().enumerate() {
        if service.id.is_empty() || service.name.is_empty() || service.provider_id.is_empty() {
            bail!("MICROSERVICES_JSON[{index}] id/name/providerId must be non-empty");
        }
        if service.deployment.mode.is_empty() {
            service.deployment.mode = "unidesk-direct".to_string();
        }
        if service.deployment.mode != "unidesk-direct"
            && service.deployment.mode != "k3sctl-managed"
            && service.deployment.mode != "internal-sidecar"
        {
            bail!("MICROSERVICES_JSON[{index}].deployment.mode must be unidesk-direct, k3sctl-managed, or internal-sidecar");
        }
        service.backend.allowed_methods = service
            .backend
            .allowed_methods
            .iter()
            .map(|method| method.to_uppercase())
            .collect();
    }
    Ok(services)
}

fn optional_env(name: &str) -> String {
    env::var(name)
        .unwrap_or_default()
        .trim()
        .to_string()
}

fn database_name_from_url(database_url: &str) -> String {
    url::Url::parse(database_url)
        .ok()
        .map(|url| url.path().trim_start_matches('/').to_string())
        .unwrap_or_default()
}

fn runtime_identity(database_url: &str) -> RuntimeIdentity {
    RuntimeIdentity {
        environment: {
            let value = optional_env("UNIDESK_ENV");
            if value.is_empty() {
                "prod".to_string()
            } else {
                value
            }
        },
        namespace: optional_env("UNIDESK_NAMESPACE"),
        database_name: {
            let explicit = optional_env("UNIDESK_DATABASE_NAME");
            if !explicit.is_empty() {
                explicit
            } else {
                let legacy = optional_env("UNIDESK_DEV_DATABASE_NAME");
                if !legacy.is_empty() {
                    legacy
                } else {
                    database_name_from_url(database_url)
                }
            }
        },
        deploy_ref: optional_env("UNIDESK_DEPLOY_REF"),
        service_id: {
            let value = optional_env("UNIDESK_DEPLOY_SERVICE_ID");
            if value.is_empty() {
                "backend-core".to_string()
            } else {
                value
            }
        },
        repo: optional_env("UNIDESK_DEPLOY_REPO"),
        commit: optional_env("UNIDESK_DEPLOY_COMMIT"),
        requested_commit: optional_env("UNIDESK_DEPLOY_REQUESTED_COMMIT"),
    }
}

pub fn read_config() -> anyhow::Result<RuntimeConfig> {
    let database_url = required_env("DATABASE_URL")?;
    Ok(RuntimeConfig {
        port: number_env("PORT")?,
        provider_port: number_env("PROVIDER_PORT")?,
        provider_data_port: optional_number_env("PROVIDER_DATA_PORT", 8082)?,
        database_url: database_url.clone(),
        identity: runtime_identity(&database_url),
        provider_token: required_env("PROVIDER_TOKEN")?,
        heartbeat_timeout_ms: number_env("HEARTBEAT_TIMEOUT_MS")?,
        task_pending_timeout_ms: optional_number_env("TASK_PENDING_TIMEOUT_MS", 10 * 60 * 1000)?,
        database_volume_name: required_env("DATABASE_VOLUME_NAME")?,
        database_volume_size: required_env("DATABASE_VOLUME_SIZE")?,
        pgdata_backup_staging_dir: env::var("PGDATA_BACKUP_STAGING_DIR")
            .unwrap_or_else(|_| "/data/baidu-netdisk-staging".to_string()),
        baidu_netdisk_internal_url: env::var("BAIDU_NETDISK_INTERNAL_URL")
            .unwrap_or_else(|_| "http://baidu-netdisk:4244".to_string()),
        microservices: read_microservices_env()?,
        log_file: required_env("LOG_FILE")?,
        database_pool_max: optional_number_env::<usize>("DATABASE_POOL_MAX", 4)?.clamp(1, 16),
    })
}
