use serde::Deserialize;
use std::path::Path;

use crate::upstream::doh_client::Strategy;

/// Top-level application configuration.
#[derive(Debug, Clone, Deserialize)]
pub struct AppConfig {
    /// Server configuration.
    pub server: ServerConfig,
    /// DNS listener configuration.
    #[serde(default)]
    pub dns: DnsListenerConfig,
    /// Upstream resolver configuration.
    pub upstream: UpstreamConfig,
    /// Cache configuration.
    #[allow(dead_code)] // Used once M4 caching is implemented
    pub cache: CacheConfig,
}

/// Server bind address and port.
#[derive(Debug, Clone, Deserialize)]
pub struct ServerConfig {
    /// Address to listen on (e.g., "0.0.0.0:3000").
    pub listen: String,
}

/// DNS listener configuration for UDP/TCP.
#[derive(Debug, Clone, Deserialize)]
pub struct DnsListenerConfig {
    /// Address to listen on for DNS queries (e.g., "0.0.0.0:5353").
    pub listen: String,
}

impl Default for DnsListenerConfig {
    fn default() -> Self {
        Self {
            listen: "0.0.0.0:5353".to_string(),
        }
    }
}

/// Upstream DoH resolver settings.
#[derive(Debug, Clone, Deserialize)]
pub struct UpstreamConfig {
    /// List of upstream DoH resolvers.
    pub resolvers: Vec<ResolverEntry>,
    /// Selection strategy: "round-robin" or "failover".
    pub strategy: String,
}

/// A single upstream resolver entry.
#[derive(Debug, Clone, Deserialize)]
pub struct ResolverEntry {
    /// Human-readable name (e.g., "cloudflare").
    pub name: String,
    /// DoH endpoint URL.
    pub url: String,
}

/// Cache settings.
#[derive(Debug, Clone, Deserialize)]
#[allow(dead_code)] // Fields used once M3 caching is implemented
pub struct CacheConfig {
    /// Whether caching is enabled.
    pub enabled: bool,
    /// Maximum number of cache entries.
    pub max_entries: usize,
    /// Maximum TTL in seconds (caps per-record TTL).
    pub max_ttl_secs: u64,
}

impl AppConfig {
    /// Load configuration from a TOML file, then apply env var overrides.
    pub fn load(path: &Path) -> anyhow::Result<Self> {
        let content = std::fs::read_to_string(path)
            .map_err(|e| anyhow::anyhow!("failed to read config file {:?}: {}", path, e))?;
        let mut config: AppConfig = toml::from_str(&content)?;
        config.apply_env_overrides();
        Ok(config)
    }

    /// Override config values from environment variables.
    ///
    /// Supported variables:
    /// - `DOH_LISTEN` — override server listen address
    /// - `DOH_UPSTREAM_STRATEGY` — override upstream selection strategy
    /// - `DOH_UPSTREAM_RESOLVERS` — comma-separated `name=url` pairs
    fn apply_env_overrides(&mut self) {
        if let Ok(listen) = std::env::var("DOH_LISTEN") {
            self.server.listen = listen;
        }
        if let Ok(strategy) = std::env::var("DOH_UPSTREAM_STRATEGY") {
            self.upstream.strategy = strategy;
        }
        if let Ok(resolvers) = std::env::var("DOH_UPSTREAM_RESOLVERS") {
            let entries: Vec<ResolverEntry> = resolvers
                .split(',')
                .filter_map(|entry| {
                    let (name, url) = entry.trim().split_once('=')?;
                    Some(ResolverEntry {
                        name: name.trim().to_string(),
                        url: url.trim().to_string(),
                    })
                })
                .collect();
            if !entries.is_empty() {
                self.upstream.resolvers = entries;
            }
        }
    }
}

impl UpstreamConfig {
    /// Parse the strategy string into a [`Strategy`] enum.
    pub fn strategy(&self) -> anyhow::Result<Strategy> {
        match self.strategy.as_str() {
            "round-robin" => Ok(Strategy::RoundRobin),
            "failover" => Ok(Strategy::Failover),
            other => Err(anyhow::anyhow!(
                "unknown upstream strategy: {other:?} (expected \"round-robin\" or \"failover\")"
            )),
        }
    }
}

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

    #[test]
    fn test_parse_default_config() {
        let config_str = r#"
[server]
listen = "127.0.0.1:8080"

[upstream]
resolvers = [
    { name = "cloudflare", url = "https://cloudflare-dns.com/dns-query" },
]
strategy = "round-robin"

[cache]
enabled = true
max_entries = 5000
max_ttl_secs = 300
"#;
        let config: AppConfig = toml::from_str(config_str).unwrap();
        assert_eq!(config.server.listen, "127.0.0.1:8080");
        assert_eq!(config.upstream.resolvers.len(), 1);
        assert_eq!(config.upstream.resolvers[0].name, "cloudflare");
        assert_eq!(config.upstream.strategy, "round-robin");
        assert!(config.cache.enabled);
        assert_eq!(config.cache.max_entries, 5000);
    }

    #[test]
    fn test_parse_multiple_resolvers() {
        let config_str = r#"
[server]
listen = "0.0.0.0:3000"

[upstream]
resolvers = [
    { name = "cloudflare", url = "https://cloudflare-dns.com/dns-query" },
    { name = "google", url = "https://dns.google/dns-query" },
    { name = "quad9", url = "https://dns.quad9.net/dns-query" },
]
strategy = "failover"

[cache]
enabled = false
max_entries = 0
max_ttl_secs = 0
"#;
        let config: AppConfig = toml::from_str(config_str).unwrap();
        assert_eq!(config.upstream.resolvers.len(), 3);
        assert_eq!(config.upstream.strategy, "failover");
        assert!(!config.cache.enabled);
    }

    #[test]
    fn test_env_override_listen() {
        let config_str = r#"
[server]
listen = "127.0.0.1:8080"

[upstream]
resolvers = [{ name = "cf", url = "https://cf/dns-query" }]
strategy = "round-robin"

[cache]
enabled = false
max_entries = 0
max_ttl_secs = 0
"#;
        let mut config: AppConfig = toml::from_str(config_str).unwrap();
        std::env::set_var("DOH_LISTEN", "0.0.0.0:9999");
        config.apply_env_overrides();
        assert_eq!(config.server.listen, "0.0.0.0:9999");
        std::env::remove_var("DOH_LISTEN");
    }

    #[test]
    fn test_env_override_resolvers() {
        let config_str = r#"
[server]
listen = "127.0.0.1:8080"

[upstream]
resolvers = [{ name = "cf", url = "https://cf/dns-query" }]
strategy = "round-robin"

[cache]
enabled = false
max_entries = 0
max_ttl_secs = 0
"#;
        let mut config: AppConfig = toml::from_str(config_str).unwrap();
        std::env::set_var(
            "DOH_UPSTREAM_RESOLVERS",
            "google=https://dns.google/dns-query,quad9=https://dns.quad9.net/dns-query",
        );
        config.apply_env_overrides();
        assert_eq!(config.upstream.resolvers.len(), 2);
        assert_eq!(config.upstream.resolvers[0].name, "google");
        assert_eq!(config.upstream.resolvers[1].name, "quad9");
        std::env::remove_var("DOH_UPSTREAM_RESOLVERS");
    }
}
