//! Configuration parsing for Aphoria.

use std::path::{Path, PathBuf};

use serde::Deserialize;

use crate::AphoriaError;

/// Top-level Aphoria configuration.
///
/// Loaded from `aphoria.toml` at the project root.
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(default)]
pub struct AphoriaConfig {
    /// Project settings.
    pub project: ProjectConfig,

    /// Episteme instance settings.
    pub episteme: EpistemeConfig,

    /// Conflict threshold settings.
    pub thresholds: ThresholdConfig,

    /// Extractor settings.
    pub extractors: ExtractorConfig,

    /// Scan settings.
    pub scan: ScanConfig,

    /// Alias suggestion settings.
    pub aliases: AliasConfig,
}

impl AphoriaConfig {
    /// Load configuration from a TOML file.
    pub fn from_file(path: &Path) -> Result<Self, AphoriaError> {
        if !path.exists() {
            return Err(AphoriaError::ConfigNotFound(path.to_path_buf()));
        }

        let content = std::fs::read_to_string(path)?;
        let config: AphoriaConfig = toml::from_str(&content)?;
        Ok(config)
    }
}

/// Project identification settings.
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(default)]
pub struct ProjectConfig {
    /// Project name (auto-detected if not specified).
    pub name: Option<String>,

    /// Primary language (auto-detected if not specified).
    pub language: Option<String>,
}

/// Episteme instance configuration.
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct EpistemeConfig {
    /// Path to local Episteme data directory.
    pub data_dir: PathBuf,

    /// Remote Episteme URL (future feature).
    pub url: Option<String>,
}

impl Default for EpistemeConfig {
    fn default() -> Self {
        Self { data_dir: dirs_default_data_dir(), url: None }
    }
}

/// Conflict threshold configuration.
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct ThresholdConfig {
    /// Conflict score at or above which to BLOCK.
    pub block: f32,

    /// Conflict score at or above which to FLAG.
    pub flag: f32,
}

impl Default for ThresholdConfig {
    fn default() -> Self {
        Self { block: 0.7, flag: 0.4 }
    }
}

/// Extractor configuration.
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct ExtractorConfig {
    /// Enabled extractors.
    pub enabled: Vec<String>,

    /// Disabled extractors (alternative to enabled list).
    pub disabled: Vec<String>,

    /// Timeout extractor settings.
    pub timeout_config: TimeoutExtractorConfig,

    /// Dependency version extractor settings.
    pub dep_versions: DepVersionConfig,
}

impl Default for ExtractorConfig {
    fn default() -> Self {
        Self {
            enabled: vec![
                "tls_verify".to_string(),
                "jwt_config".to_string(),
                "hardcoded_secrets".to_string(),
                "timeout_config".to_string(),
                "dep_versions".to_string(),
                "cors_config".to_string(),
                "rate_limit".to_string(),
            ],
            disabled: vec![],
            timeout_config: TimeoutExtractorConfig::default(),
            dep_versions: DepVersionConfig::default(),
        }
    }
}

/// Timeout extractor configuration.
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct TimeoutExtractorConfig {
    /// Minimum reasonable timeout in milliseconds.
    pub min_reasonable_ms: u64,

    /// Maximum reasonable timeout in milliseconds.
    pub max_reasonable_ms: u64,
}

impl Default for TimeoutExtractorConfig {
    fn default() -> Self {
        Self { min_reasonable_ms: 1000, max_reasonable_ms: 300_000 }
    }
}

/// Dependency version extractor configuration.
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct DepVersionConfig {
    /// Path to advisory database.
    pub advisory_db: PathBuf,
}

impl Default for DepVersionConfig {
    fn default() -> Self {
        Self { advisory_db: dirs_default_advisory_db() }
    }
}

/// Scan configuration.
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct ScanConfig {
    /// Directories to exclude from scanning.
    pub exclude: Vec<String>,

    /// Maximum file size to scan (bytes).
    pub max_file_size: u64,

    /// Whether to include test files.
    pub include_tests: bool,
}

impl Default for ScanConfig {
    fn default() -> Self {
        Self {
            exclude: vec![
                "target/".to_string(),
                "node_modules/".to_string(),
                ".git/".to_string(),
                "vendor/".to_string(),
            ],
            max_file_size: 1_048_576, // 1MB
            include_tests: false,
        }
    }
}

/// Alias suggestion configuration.
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct AliasConfig {
    /// Whether to auto-suggest aliases for shared concepts.
    pub auto_suggest: bool,

    /// Whether to auto-accept aliases to Tier 0 sources.
    pub auto_accept_tier0: bool,
}

impl Default for AliasConfig {
    fn default() -> Self {
        Self { auto_suggest: true, auto_accept_tier0: true }
    }
}

/// Get the default Aphoria data directory.
fn dirs_default_data_dir() -> PathBuf {
    if let Some(home) = dirs::home_dir() {
        home.join(".aphoria").join("db")
    } else {
        PathBuf::from(".aphoria/db")
    }
}

/// Get the default advisory database directory.
fn dirs_default_advisory_db() -> PathBuf {
    if let Some(home) = dirs::home_dir() {
        home.join(".aphoria").join("advisory-db")
    } else {
        PathBuf::from(".aphoria/advisory-db")
    }
}

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

    #[test]
    fn test_default_config() {
        let config = AphoriaConfig::default();

        assert_eq!(config.thresholds.block, 0.7);
        assert_eq!(config.thresholds.flag, 0.4);
        assert!(config.extractors.enabled.contains(&"tls_verify".to_string()));
        assert!(config.scan.exclude.contains(&"target/".to_string()));
    }

    #[test]
    fn test_config_parse() {
        let toml = r#"
[project]
name = "testproject"
language = "rust"

[thresholds]
block = 0.8
flag = 0.5

[scan]
exclude = ["build/", "dist/"]
"#;

        let config: AphoriaConfig = toml::from_str(toml).expect("should parse");

        assert_eq!(config.project.name, Some("testproject".to_string()));
        assert_eq!(config.project.language, Some("rust".to_string()));
        assert_eq!(config.thresholds.block, 0.8);
        assert_eq!(config.thresholds.flag, 0.5);
        assert!(config.scan.exclude.contains(&"build/".to_string()));
    }
}
