use mad_hatter::json_store;
use std::fs;
use tempfile::TempDir;

// --- Basic declaration with mixed field types ---

json_store!(Settings, "settings.json" {
    api_key: Option<String>,
    model: Option<String>,
    temperature: Option<f64> = 0.7,
    max_retries: Option<u32> = 3,
    label: Option<String> = "default",
});

// --- Minimal declaration (no defaults) ---

json_store!(MinimalStore, "minimal.json" {
    name: Option<String>,
    count: Option<u32>,
});

// --- All defaults ---

json_store!(AllDefaults, "all_defaults.json" {
    host: Option<String> = "localhost",
    port: Option<u16> = 8080,
    debug: Option<bool> = false,
});

// --- With Vec fields (for empty=none merge semantics) ---

json_store!(VecStore, "vec_store.json" {
    tags: Option<Vec<String>>,
    scores: Option<Vec<i32>>,
    name: Option<String>,
});



#[test]
fn test_file_name_constant() {
    assert_eq!(Settings::FILE_NAME, "settings.json");
    assert_eq!(MinimalStore::FILE_NAME, "minimal.json");
    assert_eq!(AllDefaults::FILE_NAME, "all_defaults.json");
}

#[test]
fn test_default_is_all_none() {
    let s = Settings::default();
    assert!(s.api_key.is_none());
    assert!(s.model.is_none());
    assert!(s.temperature.is_none());
    assert!(s.max_retries.is_none());
    assert!(s.label.is_none());
}

#[test]
fn test_defaults_fills_declared_defaults() {
    let d = Settings::defaults();
    assert!(d.api_key.is_none()); // no default declared
    assert!(d.model.is_none());   // no default declared
    assert_eq!(d.temperature, Some(0.7));
    assert_eq!(d.max_retries, Some(3));
    assert_eq!(d.label, Some("default".into()));
}

#[test]
fn test_accessor_or_default_with_value() {
    let mut s = Settings::default();
    s.temperature = Some(1.0);
    s.max_retries = Some(10);
    s.label = Some("custom".into());

    assert_eq!(s.temperature_or_default(), 1.0);
    assert_eq!(s.max_retries_or_default(), 10);
    assert_eq!(s.label_or_default(), "custom");
}

#[test]
fn test_accessor_or_default_without_value() {
    let s = Settings::default();
    assert_eq!(s.temperature_or_default(), 0.7);
    assert_eq!(s.max_retries_or_default(), 3);
    assert_eq!(s.label_or_default(), "default");
}

#[test]
fn test_all_defaults_accessors() {
    let d = AllDefaults::default();
    assert_eq!(d.host_or_default(), "localhost");
    assert_eq!(d.port_or_default(), 8080);
    assert_eq!(d.debug_or_default(), false);
}

#[test]
fn test_merge_fallback_fills_none() {
    let mut primary = Settings::default();
    primary.api_key = Some("my-key".into());

    let fallback = Settings {
        api_key: Some("fallback-key".into()),
        model: Some("gpt-4".into()),
        temperature: Some(0.5),
        max_retries: Some(5),
        label: Some("fallback".into()),
    };

    primary.merge_fallback(&fallback);

    // api_key was already set — not overwritten
    assert_eq!(primary.api_key, Some("my-key".into()));
    // others filled from fallback
    assert_eq!(primary.model, Some("gpt-4".into()));
    assert_eq!(primary.temperature, Some(0.5));
    assert_eq!(primary.max_retries, Some(5));
    assert_eq!(primary.label, Some("fallback".into()));
}

#[test]
fn test_merge_fallback_preserves_existing() {
    let mut primary = Settings {
        api_key: Some("a".into()),
        model: Some("b".into()),
        temperature: Some(1.0),
        max_retries: Some(1),
        label: Some("c".into()),
    };

    let fallback = Settings {
        api_key: Some("x".into()),
        model: Some("y".into()),
        temperature: Some(0.0),
        max_retries: Some(99),
        label: Some("z".into()),
    };

    primary.merge_fallback(&fallback);

    // nothing changed
    assert_eq!(primary.api_key, Some("a".into()));
    assert_eq!(primary.model, Some("b".into()));
    assert_eq!(primary.temperature, Some(1.0));
    assert_eq!(primary.max_retries, Some(1));
    assert_eq!(primary.label, Some("c".into()));
}

#[test]
fn test_load_nonexistent_returns_default() {
    let dir = TempDir::new().unwrap();
    let s = Settings::load(dir.path().to_str().unwrap());
    assert!(s.api_key.is_none());
    assert!(s.temperature.is_none());
}

#[test]
fn test_save_and_load_roundtrip() {
    let dir = TempDir::new().unwrap();
    let base = dir.path().to_str().unwrap();

    let original = Settings {
        api_key: Some("sk-test".into()),
        model: Some("gpt-4o".into()),
        temperature: Some(0.9),
        max_retries: Some(7),
        label: Some("production".into()),
    };

    original.save(base).unwrap();

    // Verify file exists
    let file_path = dir.path().join("settings.json");
    assert!(file_path.exists());

    // Verify JSON content is pretty-printed
    let content = fs::read_to_string(&file_path).unwrap();
    assert!(content.contains("sk-test"));
    assert!(content.contains("gpt-4o"));

    // Load back
    let loaded = Settings::load(base);
    assert_eq!(loaded.api_key, Some("sk-test".into()));
    assert_eq!(loaded.model, Some("gpt-4o".into()));
    assert_eq!(loaded.temperature, Some(0.9));
    assert_eq!(loaded.max_retries, Some(7));
    assert_eq!(loaded.label, Some("production".into()));
}

#[test]
fn test_load_partial_json() {
    let dir = TempDir::new().unwrap();
    let file_path = dir.path().join("settings.json");

    // Write partial JSON (only some fields)
    fs::write(&file_path, r#"{"api_key": "partial-key", "temperature": 0.3}"#).unwrap();

    let s = Settings::load(dir.path().to_str().unwrap());
    assert_eq!(s.api_key, Some("partial-key".into()));
    assert_eq!(s.temperature, Some(0.3));
    assert!(s.model.is_none());
    assert!(s.max_retries.is_none());
    assert!(s.label.is_none());
}

#[test]
fn test_load_malformed_json_returns_default() {
    let dir = TempDir::new().unwrap();
    let file_path = dir.path().join("settings.json");

    fs::write(&file_path, "not valid json{{{").unwrap();

    let s = Settings::load(dir.path().to_str().unwrap());
    // Should return default (all None) on parse error
    assert!(s.api_key.is_none());
    assert!(s.temperature.is_none());
}

#[test]
fn test_save_creates_parent_directories() {
    let dir = TempDir::new().unwrap();
    let base = dir.path().join("nested").join("deep");
    let base_str = base.to_str().unwrap();

    let s = Settings::defaults();
    s.save(base_str).unwrap();

    assert!(base.join("settings.json").exists());
}

#[test]
fn test_serialize_deserialize() {
    let s = Settings {
        api_key: Some("key".into()),
        model: None,
        temperature: Some(0.5),
        max_retries: None,
        label: Some("test".into()),
    };

    let json = serde_json::to_string(&s).unwrap();
    let deserialized: Settings = serde_json::from_str(&json).unwrap();

    assert_eq!(deserialized.api_key, Some("key".into()));
    assert!(deserialized.model.is_none());
    assert_eq!(deserialized.temperature, Some(0.5));
    assert!(deserialized.max_retries.is_none());
    assert_eq!(deserialized.label, Some("test".into()));
}

#[test]
fn test_clone() {
    let s = Settings {
        api_key: Some("key".into()),
        model: None,
        temperature: Some(0.5),
        max_retries: Some(3),
        label: None,
    };

    let cloned = s.clone();
    assert_eq!(cloned.api_key, s.api_key);
    assert_eq!(cloned.temperature, s.temperature);
    assert_eq!(cloned.max_retries, s.max_retries);
}

#[test]
fn test_debug() {
    let s = Settings::default();
    let debug = format!("{:?}", s);
    assert!(debug.contains("Settings"));
}

#[test]
fn test_minimal_store() {
    let dir = TempDir::new().unwrap();
    let base = dir.path().to_str().unwrap();

    let mut s = MinimalStore::default();
    s.name = Some("test".into());
    s.count = Some(42);

    s.save(base).unwrap();
    let loaded = MinimalStore::load(base);
    assert_eq!(loaded.name, Some("test".into()));
    assert_eq!(loaded.count, Some(42));
}

#[test]
fn test_defaults_and_merge_workflow() {
    // Simulate the real workflow:
    // 1. Load from file (may be partial)
    // 2. Merge with defaults
    // 3. Access with _or_default

    let dir = TempDir::new().unwrap();
    let file_path = dir.path().join("settings.json");
    fs::write(&file_path, r#"{"api_key": "user-key"}"#).unwrap();

    let mut s = Settings::load(dir.path().to_str().unwrap());
    assert_eq!(s.api_key, Some("user-key".into()));
    assert!(s.temperature.is_none());

    // Merge with defaults
    let defaults = Settings::defaults();
    s.merge_fallback(&defaults);

    // api_key preserved, defaults filled
    assert_eq!(s.api_key, Some("user-key".into()));
    assert_eq!(s.temperature, Some(0.7));
    assert_eq!(s.max_retries, Some(3));
    assert_eq!(s.label, Some("default".into()));

    // Accessor works
    assert_eq!(s.temperature_or_default(), 0.7);
}

// =============================================
// Tests for skip_serializing_if (Enhancement 1)
// =============================================

#[test]
fn test_save_skips_none_fields() {
    let dir = TempDir::new().unwrap();
    let base = dir.path().to_str().unwrap();

    // Only set some fields, leave others as None
    let mut s = Settings::default();
    s.api_key = Some("my-key".into());
    s.temperature = Some(0.9);

    s.save(base).unwrap();

    let content = fs::read_to_string(dir.path().join("settings.json")).unwrap();

    // Set fields should be present
    assert!(content.contains("api_key"));
    assert!(content.contains("my-key"));
    assert!(content.contains("temperature"));

    // None fields should NOT be present (no "null" values)
    assert!(!content.contains("model"));
    assert!(!content.contains("max_retries"));
    assert!(!content.contains("label"));
    assert!(!content.contains("null"));
}

#[test]
fn test_save_skip_none_roundtrip() {
    // Verify that save (skipping nulls) + load still works correctly
    let dir = TempDir::new().unwrap();
    let base = dir.path().to_str().unwrap();

    let original = Settings {
        api_key: Some("key".into()),
        model: None,
        temperature: Some(0.5),
        max_retries: None,
        label: None,
    };

    original.save(base).unwrap();
    let loaded = Settings::load(base);

    assert_eq!(loaded.api_key, Some("key".into()));
    assert!(loaded.model.is_none());
    assert_eq!(loaded.temperature, Some(0.5));
    assert!(loaded.max_retries.is_none());
    assert!(loaded.label.is_none());
}

#[test]
fn test_save_all_none_produces_empty_object() {
    let dir = TempDir::new().unwrap();
    let base = dir.path().to_str().unwrap();

    let s = Settings::default();
    s.save(base).unwrap();

    let content = fs::read_to_string(dir.path().join("settings.json")).unwrap();
    // Should be just an empty JSON object (no null fields)
    let trimmed = content.trim();
    assert_eq!(trimmed, "{}");
}

// =============================================
// Tests for Option<Vec<T>> merge (Enhancement 2)
// =============================================

#[test]
fn test_vec_merge_none_gets_fallback() {
    let mut primary = VecStore::default();
    // All None

    let fallback = VecStore {
        tags: Some(vec!["rust".into(), "async".into()]),
        scores: Some(vec![1, 2, 3]),
        name: Some("fallback".into()),
    };

    primary.merge_fallback(&fallback);

    assert_eq!(primary.tags, Some(vec!["rust".into(), "async".into()]));
    assert_eq!(primary.scores, Some(vec![1, 2, 3]));
    assert_eq!(primary.name, Some("fallback".into()));
}

#[test]
fn test_vec_merge_empty_vec_gets_fallback() {
    // Empty Vec should be treated as "not set" — fallback should fill it
    let mut primary = VecStore {
        tags: Some(vec![]),      // empty vec — should trigger fallback
        scores: Some(vec![]),    // empty vec — should trigger fallback
        name: Some("kept".into()), // non-Vec Option — is_some, should NOT be overwritten
    };

    let fallback = VecStore {
        tags: Some(vec!["fallback-tag".into()]),
        scores: Some(vec![99]),
        name: Some("fallback-name".into()),
    };

    primary.merge_fallback(&fallback);

    // Vec fields: empty was treated as unset, so fallback fills them
    assert_eq!(primary.tags, Some(vec!["fallback-tag".into()]));
    assert_eq!(primary.scores, Some(vec![99]));
    // Non-Vec field: was Some, so NOT overwritten
    assert_eq!(primary.name, Some("kept".into()));
}

#[test]
fn test_vec_merge_nonempty_vec_preserved() {
    let mut primary = VecStore {
        tags: Some(vec!["original".into()]),
        scores: Some(vec![42]),
        name: None,
    };

    let fallback = VecStore {
        tags: Some(vec!["fallback".into()]),
        scores: Some(vec![0]),
        name: Some("fb".into()),
    };

    primary.merge_fallback(&fallback);

    // Non-empty Vecs preserved
    assert_eq!(primary.tags, Some(vec!["original".into()]));
    assert_eq!(primary.scores, Some(vec![42]));
    // Non-Vec None field filled from fallback
    assert_eq!(primary.name, Some("fb".into()));
}

#[test]
fn test_vec_save_skips_none() {
    let dir = TempDir::new().unwrap();
    let base = dir.path().to_str().unwrap();

    let s = VecStore {
        tags: Some(vec!["a".into()]),
        scores: None,
        name: None,
    };

    s.save(base).unwrap();

    let content = fs::read_to_string(dir.path().join("vec_store.json")).unwrap();
    assert!(content.contains("tags"));
    assert!(!content.contains("scores"));
    assert!(!content.contains("name"));
    assert!(!content.contains("null"));
}