use std::io::Write;
use tempfile::TempDir;

// Each test uses a unique struct name to avoid OnceLock conflicts across tests.
// With staged init, JSON paths use `{field}` template references resolved from env.

mod basic {
    use super::*;

    mad_hatter::unified_params!(BasicParams, "TEST", "{base}/params.json" {
        #[required]
        base: String,
        #[required]
        port: u16,
        #[readonly]
        secret: String = "default-secret",
        #[readonly]
        debug: bool = false,
        timeout_secs: u64 = 300,
        label: String = "hello",
    });

    #[test]
    fn test_required_field_from_env() {
        let dir = TempDir::new().unwrap();
        std::env::set_var("TEST_BASE", dir.path().to_str().unwrap());
        std::env::set_var("TEST_PORT", "9090");
        std::env::set_var("TEST_SECRET", "my-secret");
        BasicParams::init();

        assert_eq!(BasicParams::port(), 9090);
        assert_eq!(BasicParams::secret(), "my-secret");
        assert_eq!(BasicParams::debug(), false);
        assert_eq!(BasicParams::timeout_secs(), 300);
        assert_eq!(BasicParams::label(), "hello");

        std::env::remove_var("TEST_BASE");
        std::env::remove_var("TEST_PORT");
        std::env::remove_var("TEST_SECRET");
    }
}

mod json_merge {
    use super::*;

    mad_hatter::unified_params!(JsonParams, "JP", "{dir}/jp.json" {
        #[required]
        dir: String,
        #[required]
        name: String,
        #[readonly]
        host: String = "localhost",
        max_conn: u64 = 10,
    });

    #[test]
    fn test_json_overrides_default_for_mutable() {
        let dir = TempDir::new().unwrap();
        std::env::set_var("JP_DIR", dir.path().to_str().unwrap());
        std::env::set_var("JP_NAME", "test-app");

        let json_path = dir.path().join("jp.json");
        let mut f = std::fs::File::create(&json_path).unwrap();
        write!(f, r#"{{"max_conn": 50}}"#).unwrap();

        JsonParams::init();

        assert_eq!(JsonParams::name(), "test-app");
        assert_eq!(JsonParams::host(), "localhost");
        assert_eq!(JsonParams::max_conn(), 50);

        std::env::remove_var("JP_DIR");
        std::env::remove_var("JP_NAME");
    }
}

mod readonly_ignores_json {
    use super::*;

    mad_hatter::unified_params!(RoParams, "RO", "{dir}/ro.json" {
        #[required]
        dir: String,
        #[required]
        id: u16,
        #[readonly]
        mode: String = "prod",
        limit: u64 = 100,
    });

    #[test]
    fn test_readonly_ignores_json() {
        let dir = TempDir::new().unwrap();
        std::env::set_var("RO_DIR", dir.path().to_str().unwrap());
        std::env::set_var("RO_ID", "1");

        let json_path = dir.path().join("ro.json");
        std::fs::write(&json_path, r#"{"mode": "staging", "limit": 50}"#).unwrap();

        RoParams::init();

        assert_eq!(RoParams::mode(), "prod");
        assert_eq!(RoParams::limit(), 50);

        std::env::remove_var("RO_DIR");
        std::env::remove_var("RO_ID");
    }
}

mod env_over_json {
    use super::*;

    mad_hatter::unified_params!(EnvOverParams, "EO", "{dir}/eo.json" {
        #[required]
        dir: String,
        #[required]
        id: u16,
        #[readonly]
        mode: String = "prod",
        limit: u64 = 100,
    });

    #[test]
    fn test_env_overrides_everything() {
        let dir = TempDir::new().unwrap();
        std::env::set_var("EO_DIR", dir.path().to_str().unwrap());
        std::env::set_var("EO_ID", "1");
        std::env::set_var("EO_MODE", "dev");
        std::env::set_var("EO_LIMIT", "999");

        let json_path = dir.path().join("eo.json");
        std::fs::write(&json_path, r#"{"limit": 50}"#).unwrap();

        EnvOverParams::init();

        assert_eq!(EnvOverParams::mode(), "dev");
        assert_eq!(EnvOverParams::limit(), 999);

        std::env::remove_var("EO_DIR");
        std::env::remove_var("EO_ID");
        std::env::remove_var("EO_MODE");
        std::env::remove_var("EO_LIMIT");
    }
}

mod mutable_setter {
    use super::*;

    mad_hatter::unified_params!(MutParams, "MUT", "{dir}/mut.json" {
        #[required]
        dir: String,
        #[required]
        key: String,
        #[readonly]
        frozen: u64 = 42,
        adjustable: u64 = 10,
        tag: String = "initial",
    });

    #[test]
    fn test_setter_and_save() {
        let dir = TempDir::new().unwrap();
        std::env::set_var("MUT_DIR", dir.path().to_str().unwrap());
        std::env::set_var("MUT_KEY", "k1");
        MutParams::init();

        assert_eq!(MutParams::adjustable(), 10);
        assert_eq!(MutParams::tag(), "initial");

        MutParams::set_adjustable(77);
        MutParams::set_tag("changed".to_string());

        assert_eq!(MutParams::adjustable(), 77);
        assert_eq!(MutParams::tag(), "changed");

        let json_path = dir.path().join("mut.json");
        let content = std::fs::read_to_string(&json_path).unwrap();
        let v: serde_json::Value = serde_json::from_str(&content).unwrap();
        assert_eq!(v["adjustable"], 77);
        assert_eq!(v["tag"], "changed");
        assert!(v.get("frozen").is_none() || v["frozen"].is_null());
        assert!(v.get("key").is_none() || v["key"].is_null());

        std::env::remove_var("MUT_DIR");
        std::env::remove_var("MUT_KEY");
    }
}

mod option_fields {
    use super::*;

    mad_hatter::unified_params!(OptParams, "OPT", "{dir}/opt.json" {
        #[required]
        dir: String,
        #[required]
        id: u16,
        #[readonly]
        host: Option<String>,
        #[readonly]
        flag: Option<bool>,
        extra: Option<u64>,
    });

    #[test]
    fn test_option_none_by_default() {
        let dir = TempDir::new().unwrap();
        std::env::set_var("OPT_DIR", dir.path().to_str().unwrap());
        std::env::set_var("OPT_ID", "1");
        OptParams::init();

        assert_eq!(OptParams::host(), None);
        assert_eq!(OptParams::flag(), None);
        assert_eq!(OptParams::extra(), None);

        std::env::remove_var("OPT_DIR");
        std::env::remove_var("OPT_ID");
    }
}

mod option_from_env {
    use super::*;

    mad_hatter::unified_params!(OptEnvParams, "OE", "{dir}/oe.json" {
        #[required]
        dir: String,
        #[required]
        id: u16,
        #[readonly]
        host: Option<String>,
        count: Option<u64>,
    });

    #[test]
    fn test_option_from_env() {
        let dir = TempDir::new().unwrap();
        std::env::set_var("OE_DIR", dir.path().to_str().unwrap());
        std::env::set_var("OE_ID", "2");
        std::env::set_var("OE_HOST", "example.com");
        std::env::set_var("OE_COUNT", "42");

        OptEnvParams::init();

        assert_eq!(OptEnvParams::host(), Some("example.com".to_string()));
        assert_eq!(OptEnvParams::count(), Some(42));

        std::env::remove_var("OE_DIR");
        std::env::remove_var("OE_ID");
        std::env::remove_var("OE_HOST");
        std::env::remove_var("OE_COUNT");
    }
}

mod option_from_json {
    use super::*;

    mad_hatter::unified_params!(OptJsonParams, "OJ", "{dir}/oj.json" {
        #[required]
        dir: String,
        #[required]
        id: u16,
        #[readonly]
        host: Option<String>,
        count: Option<u64>,
    });

    #[test]
    fn test_option_mutable_from_json() {
        let dir = TempDir::new().unwrap();
        std::env::set_var("OJ_DIR", dir.path().to_str().unwrap());
        std::env::set_var("OJ_ID", "3");
        let json_path = dir.path().join("oj.json");
        std::fs::write(&json_path, r#"{"host": "json-host", "count": 99}"#).unwrap();

        OptJsonParams::init();

        assert_eq!(OptJsonParams::host(), None);
        assert_eq!(OptJsonParams::count(), Some(99));

        std::env::remove_var("OJ_DIR");
        std::env::remove_var("OJ_ID");
    }
}

mod custom_env {
    use super::*;

    mad_hatter::unified_params!(CustomEnvParams, "CE", "{dir}/ce.json" {
        #[required]
        dir: String,
        #[required]
        id: u16,
        #[env("MY_CUSTOM_VAR")]
        #[readonly]
        special: String = "default",
    });

    #[test]
    fn test_custom_env_name() {
        let dir = TempDir::new().unwrap();
        std::env::set_var("CE_DIR", dir.path().to_str().unwrap());
        std::env::set_var("CE_ID", "1");
        std::env::set_var("MY_CUSTOM_VAR", "custom-value");

        CustomEnvParams::init();

        assert_eq!(CustomEnvParams::special(), "custom-value");

        std::env::remove_var("CE_DIR");
        std::env::remove_var("CE_ID");
        std::env::remove_var("MY_CUSTOM_VAR");
    }
}

mod global_access {
    use super::*;

    mad_hatter::unified_params!(GlobalParams, "GL", "{dir}/gl.json" {
        #[required]
        dir: String,
        #[required]
        port: u16,
        name: String = "world",
    });

    #[test]
    fn test_global_field_access() {
        let dir = TempDir::new().unwrap();
        std::env::set_var("GL_DIR", dir.path().to_str().unwrap());
        std::env::set_var("GL_PORT", "8080");
        GlobalParams::init();

        let g = GlobalParams::global();
        assert_eq!(g.port, 8080);
        assert_eq!(g.name, "world");

        std::env::remove_var("GL_DIR");
        std::env::remove_var("GL_PORT");
    }
}

mod option_setter {
    use super::*;

    mad_hatter::unified_params!(OptSetParams, "OS", "{dir}/os.json" {
        #[required]
        dir: String,
        #[required]
        id: u16,
        value: Option<String>,
    });

    #[test]
    fn test_option_mutable_setter() {
        let dir = TempDir::new().unwrap();
        std::env::set_var("OS_DIR", dir.path().to_str().unwrap());
        std::env::set_var("OS_ID", "1");
        OptSetParams::init();

        assert_eq!(OptSetParams::value(), None);

        OptSetParams::set_value(Some("hello".to_string()));
        assert_eq!(OptSetParams::value(), Some("hello".to_string()));

        let json_path = dir.path().join("os.json");
        let content = std::fs::read_to_string(&json_path).unwrap();
        let v: serde_json::Value = serde_json::from_str(&content).unwrap();
        assert_eq!(v["value"], "hello");

        std::env::remove_var("OS_DIR");
        std::env::remove_var("OS_ID");
    }
}

mod defaults_only {
    use super::*;

    mad_hatter::unified_params!(DefParams, "DEF", "{dir}/def.json" {
        #[required]
        dir: String,
        #[required]
        id: u16,
        #[readonly]
        ro_str: String = "readonly-default",
        #[readonly]
        ro_num: u64 = 42,
        mut_str: String = "mutable-default",
        mut_num: u64 = 99,
    });

    #[test]
    fn test_defaults_without_env_or_json() {
        let dir = TempDir::new().unwrap();
        std::env::set_var("DEF_DIR", dir.path().to_str().unwrap());
        std::env::set_var("DEF_ID", "1");
        DefParams::init();

        assert_eq!(DefParams::ro_str(), "readonly-default");
        assert_eq!(DefParams::ro_num(), 42);
        assert_eq!(DefParams::mut_str(), "mutable-default");
        assert_eq!(DefParams::mut_num(), 99);

        std::env::remove_var("DEF_DIR");
        std::env::remove_var("DEF_ID");
    }
}