use mad_hatter::env_schema;
use std::panic;

// Each test uses a unique prefix to avoid env var conflicts between tests.

// === Schema declarations ===

env_schema!(FullConfig, "FULL" {
    name: String,
    port: u16,
    debug: bool,
    label: Option<String>,
    count: Option<u32>,
    verbose: Option<bool>,
    host: String = "0.0.0.0",
    timeout: u32 = 30,
    enabled: bool = true,
    #[env("FULL_CUSTOM_KEY")]
    api_key: String,
});

env_schema!(ReqConfig, "REQ" {
    secret: String,
    port: u16,
});

env_schema!(OptConfig, "OPT" {
    name: Option<String>,
    port: Option<u16>,
    debug: Option<bool>,
});

env_schema!(DefConfig, "DEF" {
    host: String = "localhost",
    port: u16 = 8080,
    debug: bool = false,
});

env_schema!(BoolConfig, "BOOL" {
    flag: bool,
});

env_schema!(CustomConfig, "CUST" {
    #[env("CUST_MY_KEY")]
    key: String,
    #[env("CUST_MY_PORT")]
    port: u16,
});

env_schema!(MultiErrConfig, "MERR" {
    name: String,
    port: u16,
    secret: String,
});

env_schema!(UnknownConfig, "UNK" {
    name: String,
});

env_schema!(ParseFailConfig, "PF" {
    port: u16,
});

env_schema!(BoolFailConfig, "BF" {
    flag: bool,
});

env_schema!(NumTypesConfig, "NT" {
    a: u16,
    b: u32,
    c: u64,
    d: i32,
    e: i64,
    f: usize,
});

// === Helper ===

fn clear_env_prefix(prefix: &str) {
    let prefix_underscore = format!("{}_", prefix);
    let keys: Vec<String> = std::env::vars()
        .filter(|(k, _)| k.starts_with(&prefix_underscore))
        .map(|(k, _)| k)
        .collect();
    for key in keys {
        std::env::remove_var(&key);
    }
}

// === Tests ===

#[test]
fn test_all_fields_normal() {
    clear_env_prefix("FULL");
    std::env::set_var("FULL_NAME", "alice");
    std::env::set_var("FULL_PORT", "8080");
    std::env::set_var("FULL_DEBUG", "true");
    std::env::set_var("FULL_LABEL", "test");
    std::env::set_var("FULL_COUNT", "5");
    std::env::set_var("FULL_VERBOSE", "false");
    std::env::set_var("FULL_HOST", "1.2.3.4");
    std::env::set_var("FULL_TIMEOUT", "60");
    std::env::set_var("FULL_ENABLED", "0");
    std::env::set_var("FULL_CUSTOM_KEY", "my-secret");

    let cfg = FullConfig::from_env();
    assert_eq!(cfg.name, "alice");
    assert_eq!(cfg.port, 8080);
    assert_eq!(cfg.debug, true);
    assert_eq!(cfg.label, Some("test".to_string()));
    assert_eq!(cfg.count, Some(5));
    assert_eq!(cfg.verbose, Some(false));
    assert_eq!(cfg.host, "1.2.3.4");
    assert_eq!(cfg.timeout, 60);
    assert_eq!(cfg.enabled, false);
    assert_eq!(cfg.api_key, "my-secret");

    clear_env_prefix("FULL");
}

#[test]
fn test_required_missing_panics() {
    clear_env_prefix("REQ");
    // Don't set REQ_SECRET or REQ_PORT

    let result = panic::catch_unwind(|| {
        ReqConfig::from_env();
    });
    assert!(result.is_err());
    let panic_msg = result.unwrap_err();
    let msg = panic_msg.downcast_ref::<String>().unwrap();
    assert!(msg.contains("Missing required env: REQ_SECRET"), "got: {}", msg);
    assert!(msg.contains("Missing required env: REQ_PORT"), "got: {}", msg);

    clear_env_prefix("REQ");
}

#[test]
fn test_option_missing_is_none() {
    clear_env_prefix("OPT");
    // Don't set any OPT_* vars

    let cfg = OptConfig::from_env();
    assert_eq!(cfg.name, None);
    assert_eq!(cfg.port, None);
    assert_eq!(cfg.debug, None);

    clear_env_prefix("OPT");
}

#[test]
fn test_option_present_is_some() {
    clear_env_prefix("OPT");
    std::env::set_var("OPT_NAME", "hello");
    std::env::set_var("OPT_PORT", "3000");
    std::env::set_var("OPT_DEBUG", "1");

    let cfg = OptConfig::from_env();
    assert_eq!(cfg.name, Some("hello".to_string()));
    assert_eq!(cfg.port, Some(3000));
    assert_eq!(cfg.debug, Some(true));

    clear_env_prefix("OPT");
}

#[test]
fn test_default_values_used() {
    clear_env_prefix("DEF");
    // Don't set any DEF_* vars

    let cfg = DefConfig::from_env();
    assert_eq!(cfg.host, "localhost");
    assert_eq!(cfg.port, 8080);
    assert_eq!(cfg.debug, false);

    clear_env_prefix("DEF");
}

#[test]
fn test_default_values_overridden() {
    clear_env_prefix("DEF");
    std::env::set_var("DEF_HOST", "example.com");
    std::env::set_var("DEF_PORT", "9090");
    std::env::set_var("DEF_DEBUG", "true");

    let cfg = DefConfig::from_env();
    assert_eq!(cfg.host, "example.com");
    assert_eq!(cfg.port, 9090);
    assert_eq!(cfg.debug, true);

    clear_env_prefix("DEF");
}

#[test]
fn test_parse_failure_panics() {
    clear_env_prefix("PF");
    std::env::set_var("PF_PORT", "not_a_number");

    let result = panic::catch_unwind(|| {
        ParseFailConfig::from_env();
    });
    assert!(result.is_err());
    let panic_msg = result.unwrap_err();
    let msg = panic_msg.downcast_ref::<String>().unwrap();
    assert!(msg.contains("PF_PORT='not_a_number' is not a valid u16"), "got: {}", msg);

    clear_env_prefix("PF");
}

#[test]
fn test_bool_invalid_value_panics() {
    clear_env_prefix("BF");
    std::env::set_var("BF_FLAG", "yes");

    let result = panic::catch_unwind(|| {
        BoolFailConfig::from_env();
    });
    assert!(result.is_err());
    let panic_msg = result.unwrap_err();
    let msg = panic_msg.downcast_ref::<String>().unwrap();
    assert!(msg.contains("BF_FLAG='yes' is not a valid bool (expected true/false/1/0)"), "got: {}", msg);

    clear_env_prefix("BF");
}

#[test]
fn test_bool_valid_values() {
    clear_env_prefix("BOOL");

    // true
    std::env::set_var("BOOL_FLAG", "true");
    let cfg = BoolConfig::from_env();
    assert_eq!(cfg.flag, true);

    // false
    std::env::set_var("BOOL_FLAG", "false");
    let cfg = BoolConfig::from_env();
    assert_eq!(cfg.flag, false);

    // 1
    std::env::set_var("BOOL_FLAG", "1");
    let cfg = BoolConfig::from_env();
    assert_eq!(cfg.flag, true);

    // 0
    std::env::set_var("BOOL_FLAG", "0");
    let cfg = BoolConfig::from_env();
    assert_eq!(cfg.flag, false);

    clear_env_prefix("BOOL");
}

#[test]
fn test_custom_env_name() {
    clear_env_prefix("CUST");
    std::env::set_var("CUST_MY_KEY", "custom-key");
    std::env::set_var("CUST_MY_PORT", "4000");

    let cfg = CustomConfig::from_env();
    assert_eq!(cfg.key, "custom-key");
    assert_eq!(cfg.port, 4000);

    clear_env_prefix("CUST");
}

#[test]
fn test_unknown_prefix_variable_panics() {
    clear_env_prefix("UNK");
    std::env::set_var("UNK_NAME", "ok");
    std::env::set_var("UNK_TYPO_SECRET", "oops");

    let result = panic::catch_unwind(|| {
        UnknownConfig::from_env();
    });
    assert!(result.is_err());
    let panic_msg = result.unwrap_err();
    let msg = panic_msg.downcast_ref::<String>().unwrap();
    assert!(msg.contains("Unknown env: UNK_TYPO_SECRET"), "got: {}", msg);

    clear_env_prefix("UNK");
}

#[test]
fn test_multiple_errors_collected() {
    clear_env_prefix("MERR");
    // Missing all three required fields
    // Don't set anything

    let result = panic::catch_unwind(|| {
        MultiErrConfig::from_env();
    });
    assert!(result.is_err());
    let panic_msg = result.unwrap_err();
    let msg = panic_msg.downcast_ref::<String>().unwrap();
    assert!(msg.contains("Missing required env: MERR_NAME"), "got: {}", msg);
    assert!(msg.contains("Missing required env: MERR_PORT"), "got: {}", msg);
    assert!(msg.contains("Missing required env: MERR_SECRET"), "got: {}", msg);

    clear_env_prefix("MERR");
}

#[test]
fn test_numeric_types() {
    clear_env_prefix("NT");
    std::env::set_var("NT_A", "100");
    std::env::set_var("NT_B", "200");
    std::env::set_var("NT_C", "300");
    std::env::set_var("NT_D", "-10");
    std::env::set_var("NT_E", "-20");
    std::env::set_var("NT_F", "999");

    let cfg = NumTypesConfig::from_env();
    assert_eq!(cfg.a, 100u16);
    assert_eq!(cfg.b, 200u32);
    assert_eq!(cfg.c, 300u64);
    assert_eq!(cfg.d, -10i32);
    assert_eq!(cfg.e, -20i64);
    assert_eq!(cfg.f, 999usize);

    clear_env_prefix("NT");
}

#[test]
fn test_clone_and_debug() {
    clear_env_prefix("DEF");

    let cfg = DefConfig::from_env();
    let cfg2 = cfg.clone();
    assert_eq!(cfg2.host, "localhost");

    let debug_str = format!("{:?}", cfg);
    assert!(debug_str.contains("DefConfig"));

    clear_env_prefix("DEF");
}

#[test]
fn test_mixed_errors_parse_and_missing() {
    clear_env_prefix("MERR");
    // Set port to invalid, leave name and secret missing
    std::env::set_var("MERR_PORT", "xyz");

    let result = panic::catch_unwind(|| {
        MultiErrConfig::from_env();
    });
    assert!(result.is_err());
    let panic_msg = result.unwrap_err();
    let msg = panic_msg.downcast_ref::<String>().unwrap();
    assert!(msg.contains("Missing required env: MERR_NAME"), "got: {}", msg);
    assert!(msg.contains("MERR_PORT='xyz' is not a valid u16"), "got: {}", msg);
    assert!(msg.contains("Missing required env: MERR_SECRET"), "got: {}", msg);

    clear_env_prefix("MERR");
}