//! Configuration loading and management.
//!
//! Implements [[ADR-0009]] configurable source code reference scanning.

use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};

/// Project configuration (gov/config.toml)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
    /// Absolute path to `gov/` — not configurable, derived from config file location.
    #[serde(skip)]
    pub gov_root: PathBuf,
    #[serde(default)]
    pub project: ProjectConfig,
    #[serde(default)]
    pub paths: PathsConfig,
    #[serde(default)]
    pub schema: SchemaConfig,
    #[serde(default)]
    pub source_scan: SourceScanConfig,
    #[serde(default)]
    pub work_item: WorkItemConfig,
    #[serde(default)]
    pub verification: VerificationConfig,
    #[serde(default)]
    pub concurrency: ConcurrencyConfig,
}

impl Default for Config {
    fn default() -> Self {
        Self {
            gov_root: PathBuf::from("gov"),
            project: ProjectConfig::default(),
            paths: PathsConfig::default(),
            schema: SchemaConfig::default(),
            source_scan: SourceScanConfig::default(),
            work_item: WorkItemConfig::default(),
            verification: VerificationConfig::default(),
            concurrency: ConcurrencyConfig::default(),
        }
    }
}

/// Project-level verification guard policy.
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct VerificationConfig {
    #[serde(default)]
    pub enabled: bool,
    #[serde(default)]
    pub default_guards: Vec<String>,
}

/// Concurrency and write-safety configuration.
///
/// Implements [[RFC-0004]] concurrent write safety (configurable lock timeout).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConcurrencyConfig {
    /// Maximum seconds to wait for exclusive access before failing (default: 30).
    #[serde(default = "default_lock_timeout_secs")]
    pub lock_timeout_secs: u64,
}

fn default_lock_timeout_secs() -> u64 {
    30
}

impl Default for ConcurrencyConfig {
    fn default() -> Self {
        Self {
            lock_timeout_secs: default_lock_timeout_secs(),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ProjectConfig {
    #[serde(default = "default_project_name")]
    pub name: String,
    /// Default owner for new RFCs (e.g., "@your-handle" or "@org-name")
    #[serde(default = "default_owner")]
    pub default_owner: String,
}

fn default_project_name() -> String {
    "govctl-project".to_string()
}

fn default_owner() -> String {
    // Check environment variable first (useful for testing)
    if let Ok(owner) = std::env::var("GOVCTL_DEFAULT_OWNER") {
        return owner;
    }

    // Try to get git user.name, fall back to placeholder
    std::process::Command::new("git")
        .args(["config", "user.name"])
        .output()
        .ok()
        .and_then(|o| String::from_utf8(o.stdout).ok())
        .map(|s| format!("@{}", s.trim()))
        .filter(|s| s.len() > 1) // "@" alone is not valid
        .unwrap_or_else(|| "@your-handle".to_string())
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PathsConfig {
    /// Output directory for rendered docs (docs/)
    #[serde(default = "default_docs_output")]
    pub docs_output: PathBuf,
    /// AI agent directory (Claude, Cursor, Windsurf, etc.)
    /// Contains skills/ and agents/ subdirs, written by `init-skills`
    #[serde(default = "default_agent_dir")]
    pub agent_dir: PathBuf,
}

fn default_docs_output() -> PathBuf {
    PathBuf::from("docs")
}

fn default_agent_dir() -> PathBuf {
    PathBuf::from(".claude")
}

impl Default for PathsConfig {
    fn default() -> Self {
        Self {
            docs_output: default_docs_output(),
            agent_dir: default_agent_dir(),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SchemaConfig {
    #[serde(default = "default_schema_version")]
    pub version: u32,
}

fn default_schema_version() -> u32 {
    crate::cmd::migrate::CURRENT_SCHEMA_VERSION
}

impl Default for SchemaConfig {
    fn default() -> Self {
        Self {
            version: default_schema_version(),
        }
    }
}

/// Source code scanning configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SourceScanConfig {
    /// Enable source code scanning (default: false)
    #[serde(default)]
    pub enabled: bool,
    /// Glob patterns for files to include (e.g., "src/**/*.rs")
    #[serde(default = "default_scan_include")]
    pub include: Vec<String>,
    /// Glob patterns for files to exclude (e.g., "**/tests/**")
    #[serde(default)]
    pub exclude: Vec<String>,
    /// Regex pattern with capture group 1 for artifact ID
    #[serde(default = "default_scan_pattern")]
    pub pattern: String,
}

fn default_scan_include() -> Vec<String> {
    vec![
        "src/**/*.rs".to_string(),
        "crates/**/*.rs".to_string(),
        "**/*.md".to_string(),
    ]
}

fn default_scan_pattern() -> String {
    // Matches double-bracket references:
    // - [[RFC-NNNN]] or [[RFC-NNNN:C-CLAUSE]]
    // - [[ADR-NNNN]]
    // - [[WI-YYYY-MM-DD-NNN]] (sequential)
    // - [[WI-YYYY-MM-DD-HHHH-NNN]] (author-hash)
    // - [[WI-YYYY-MM-DD-HHHH]] (random)
    r"\[\[(RFC-\d{4}(?::C-[A-Z][A-Z0-9-]*)?|ADR-\d{4}|WI-\d{4}-\d{2}-\d{2}-(?:[a-f0-9]{4}(?:-\d{3})?|\d{3}))\]\]".to_string()
}

impl Default for SourceScanConfig {
    fn default() -> Self {
        Self {
            enabled: false,
            include: default_scan_include(),
            exclude: vec![],
            pattern: default_scan_pattern(),
        }
    }
}

/// Work item configuration
///
/// Implements [[ADR-0020]] configurable work item ID strategies.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorkItemConfig {
    /// ID generation strategy (default: sequential)
    #[serde(default)]
    pub id_strategy: IdStrategy,
}

impl Default for WorkItemConfig {
    fn default() -> Self {
        Self {
            id_strategy: IdStrategy::Sequential,
        }
    }
}

/// Work item ID generation strategy
///
/// - `Sequential`: `WI-YYYY-MM-DD-NNN` (default, for solo projects)
/// - `AuthorHash`: `WI-YYYY-MM-DD-{hash4}-NNN` (for multi-person teams)
/// - `Random`: `WI-YYYY-MM-DD-{rand4}` (simple uniqueness)
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum IdStrategy {
    /// Sequential numbering per day (default)
    #[default]
    Sequential,
    /// Hash of git user.email + sequential numbering per author
    AuthorHash,
    /// Random 4-char hex suffix (no sequence number)
    Random,
}

impl IdStrategy {
    /// Get the author hash (first 4 chars of sha256(git user.email))
    pub fn get_author_hash() -> Option<String> {
        let output = std::process::Command::new("git")
            .args(["config", "user.email"])
            .output()
            .ok()?;

        if !output.status.success() {
            return None;
        }

        let email = String::from_utf8(output.stdout).ok()?;
        let email = email.trim();
        if email.is_empty() {
            return None;
        }

        use sha2::{Digest, Sha256};
        let mut hasher = Sha256::new();
        hasher.update(email.as_bytes());
        let result = hasher.finalize();
        Some(format!("{:02x}{:02x}", result[0], result[1]))
    }

    /// Generate a random 4-char hex suffix
    pub fn generate_random_suffix() -> String {
        use rand::RngExt;
        let mut rng = rand::rng();
        let bytes: [u8; 2] = rng.random();
        format!("{:02x}{:02x}", bytes[0], bytes[1])
    }
}

impl Config {
    /// Load config from file or use defaults
    ///
    /// All relative paths in the config are resolved relative to the project root
    /// (the parent of gov/config.toml), not the current working directory.
    pub fn load(path: Option<&Path>) -> Result<Self> {
        let config_path = path
            .map(PathBuf::from)
            .or_else(Self::find_config)
            .unwrap_or_else(|| PathBuf::from("gov/config.toml"));

        if config_path.exists() {
            let content = std::fs::read_to_string(&config_path)
                .with_context(|| format!("Failed to read config: {}", config_path.display()))?;
            let mut config: Config = toml::from_str(&content)
                .with_context(|| format!("Failed to parse config: {}", config_path.display()))?;

            // Resolve paths to absolute. gov_root is always <project_root>/gov.
            if let Some(project_root) = config_path.parent().and_then(|p| p.parent()) {
                config.gov_root = project_root.join("gov");
                if config.paths.docs_output.is_relative() {
                    config.paths.docs_output = project_root.join(&config.paths.docs_output);
                }
                if config.paths.agent_dir.is_relative() {
                    config.paths.agent_dir = project_root.join(&config.paths.agent_dir);
                }
            }

            Ok(config)
        } else {
            // Return default config if no file exists
            Ok(Config::default())
        }
    }

    /// Find config file by walking up directory tree
    fn find_config() -> Option<PathBuf> {
        let mut current = std::env::current_dir().ok()?;
        loop {
            let config_path = current.join("gov/config.toml");
            if config_path.exists() {
                return Some(config_path);
            }
            if !current.pop() {
                return None;
            }
        }
    }

    pub fn rfc_dir(&self) -> PathBuf {
        self.gov_root.join("rfc")
    }

    pub fn adr_dir(&self) -> PathBuf {
        self.gov_root.join("adr")
    }

    pub fn work_dir(&self) -> PathBuf {
        self.gov_root.join("work")
    }

    pub fn schema_dir(&self) -> PathBuf {
        self.gov_root.join("schema")
    }

    pub fn guard_dir(&self) -> PathBuf {
        self.gov_root.join("guard")
    }

    pub fn templates_dir(&self) -> PathBuf {
        self.gov_root.join("templates")
    }

    pub fn rfc_output(&self) -> PathBuf {
        self.paths.docs_output.join("rfc")
    }

    pub fn adr_output(&self) -> PathBuf {
        self.paths.docs_output.join("adr")
    }

    pub fn work_output(&self) -> PathBuf {
        self.paths.docs_output.join("work")
    }

    pub fn releases_path(&self) -> PathBuf {
        self.gov_root.join("releases.toml")
    }

    /// Path for user-facing display: relative to project root when under it.
    pub fn display_path(&self, path: &std::path::Path) -> std::path::PathBuf {
        self.gov_root
            .parent()
            .and_then(|root| path.strip_prefix(root).ok())
            .map(std::path::PathBuf::from)
            .unwrap_or_else(|| path.to_path_buf())
    }

    /// Generate default config TOML at the given schema version.
    pub fn default_toml(schema_version: u32) -> String {
        format!(
            r#"[project]
name = "my-project"
# Default owner for new RFCs (uses git user.name if not set)
# default_owner = "@your-handle"

[paths]
docs_output = "docs"
# AI agent directory — target for `govctl init-skills`
# Contains skills/ and agents/ subdirs
# Default: ".claude" (Claude Code / Claude Desktop)
# For Cursor: ".cursor"
# For Windsurf: ".windsurf"
# agent_dir = ".claude"

[schema]
version = {schema_version}

# [work_item]
# ID strategy for work items (default: sequential)
# - sequential: WI-YYYY-MM-DD-NNN (solo projects)
# - author-hash: WI-YYYY-MM-DD-{{hash}}-NNN (multi-person teams, uses git email)
# - random: WI-YYYY-MM-DD-{{rand}} (simple uniqueness)
# id_strategy = "author-hash"

# [verification]
# Enable project-level default verification guards.
# enabled = true
# default_guards = ["GUARD-GOVCTL-CHECK", "GUARD-CARGO-TEST"]

# [source_scan]
# Scan source files for [[artifact-id]] references during `govctl check`
# enabled = false
# include = ["src/**/*.rs", "crates/**/*.rs", "**/*.md"]
# exclude = []

# [concurrency]
# Maximum seconds to wait for exclusive lock before failing (default: 30)
# Implements [[RFC-0004]] concurrent write safety
# lock_timeout_secs = 30
"#
        )
    }
}
