use platform_config::ConfigLoader;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;

/// Inner settings — matches the `ncl_sync` key in the NCL export.
#[derive(Debug, Serialize, Deserialize)]
pub struct NclSyncSettings {
    #[serde(default)]
    pub cache_dir: Option<String>,

    #[serde(default = "default_idle_timeout")]
    pub idle_timeout_secs: u64,

    #[serde(default = "default_sync_poll_ms")]
    pub sync_poll_interval_ms: u64,

    #[serde(default = "default_concurrency")]
    pub warm_concurrency: usize,

    #[serde(default)]
    pub extra_import_paths: Vec<String>,

    /// Additional directories to warm alongside the primary workspace.
    /// Useful for `$PROVISIONING/extensions/` and other shared NCL trees that live
    /// outside the active workspace but are frequently read by commands.
    /// Default: empty. Env `NCL_SYNC_EXTRA_WARM` (colon-separated) or config value.
    /// `~` is expanded.
    #[serde(default)]
    pub extra_warm_paths: Vec<String>,

    /// Filename suffixes that identify non-exportable NCL files (schemas, contracts, lib files).
    /// Files matching these suffixes are skipped during warm-up and watcher events.
    /// Default: `["-schema.ncl", "-defaults.ncl", "-constraints.ncl"]`
    #[serde(default = "default_skip_patterns")]
    pub skip_patterns: Vec<String>,

    /// Directory basenames that indicate non-exportable NCL files.
    /// Files in any parent directory whose basename matches are skipped.
    /// Default: `["schemas", "defaults", "constraints"]`
    #[serde(default = "default_skip_dirs")]
    pub skip_dirs: Vec<String>,

    /// NATS-driven event subscriber (opt-in, requires `nats` Cargo feature).
    #[serde(default)]
    pub nats: NclSyncNatsSettings,
}

/// NATS subscription settings for event-driven cache invalidation.
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct NclSyncNatsSettings {
    /// When true, connect to NATS and subscribe to `provisioning.workspace.ncl.*`.
    #[serde(default)]
    pub enabled: bool,
    /// NATS URL. Empty → uses platform-nats default (`nats://127.0.0.1:4222`).
    #[serde(default)]
    pub url: String,
}

impl Default for NclSyncSettings {
    fn default() -> Self {
        Self {
            cache_dir: None,
            idle_timeout_secs: default_idle_timeout(),
            sync_poll_interval_ms: default_sync_poll_ms(),
            warm_concurrency: default_concurrency(),
            extra_import_paths: Vec::new(),
            extra_warm_paths: Vec::new(),
            skip_patterns: default_skip_patterns(),
            skip_dirs: default_skip_dirs(),
            nats: NclSyncNatsSettings::default(),
        }
    }
}

/// Root config struct — outer key `ncl_sync` mirrors the NCL file shape.
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct NclSyncConfig {
    #[serde(default)]
    pub ncl_sync: NclSyncSettings,
}

impl NclSyncConfig {
    pub fn load_or_default() -> Self {
        Self::load().unwrap_or_default()
    }

    pub fn settings(&self) -> &NclSyncSettings {
        &self.ncl_sync
    }

    /// Resolve cache dir. Priority:
    ///   1. Explicit `cache_dir` in config file
    ///   2. Workspace-local (`<workspace>/.ncl-cache/`) if workspace provided
    ///   3. Global fallback (`~/.cache/provisioning/config-cache/`)
    pub fn resolved_cache_dir(&self, workspace: Option<&std::path::Path>) -> PathBuf {
        if let Some(dir) = &self.ncl_sync.cache_dir {
            return PathBuf::from(dir);
        }
        crate::manifest::resolve_cache_dir(workspace)
    }
}

impl ConfigLoader for NclSyncConfig {
    fn service_name() -> &'static str {
        "ncl-sync"
    }

    fn collect_env_overrides() -> serde_json::Value {
        let mut inner = serde_json::Map::new();

        if let Ok(v) = std::env::var("NCL_SYNC_DIR") {
            inner.insert("cache_dir".into(), serde_json::Value::String(v));
        }
        if let Ok(v) = std::env::var("NCL_SYNC_IDLE_TIMEOUT") {
            if let Ok(n) = v.parse::<u64>() {
                inner.insert("idle_timeout_secs".into(), serde_json::Value::Number(n.into()));
            }
        }
        if let Ok(v) = std::env::var("NCL_SYNC_CONCURRENCY") {
            if let Ok(n) = v.parse::<u64>() {
                inner.insert("warm_concurrency".into(), serde_json::Value::Number(n.into()));
            }
        }

        // When no env overrides are set, return Null to skip the merge path entirely.
        // Returning {"ncl_sync": {}} triggers a Nickel merge conflict with the contract-
        // annotated record in the NCL config file.
        if inner.is_empty() {
            return serde_json::Value::Null;
        }

        let mut root = serde_json::Map::new();
        root.insert("ncl_sync".into(), serde_json::Value::Object(inner));
        serde_json::Value::Object(root)
    }
}

fn default_idle_timeout() -> u64 { 600 }
fn default_sync_poll_ms() -> u64 { 500 }
fn default_concurrency() -> usize { 4 }
fn default_skip_patterns() -> Vec<String> {
    vec![
        "-schema.ncl".to_string(),
        "-defaults.ncl".to_string(),
        "-constraints.ncl".to_string(),
    ]
}
fn default_skip_dirs() -> Vec<String> {
    vec!["schemas".to_string(), "defaults".to_string(), "constraints".to_string()]
}
