//! Tests for configuration parsing.

use super::*;

#[test]
fn test_default_config() {
    let config = AphoriaConfig::default();

    assert_eq!(config.thresholds.block, 0.7);
    assert_eq!(config.thresholds.flag, 0.4);
    assert!(config.extractors.enabled.contains(&"tls_verify".to_string()));
    assert!(config.scan.exclude.contains(&"target/".to_string()));
    assert!(config.policies.is_empty());
    assert!(!config.hosted.is_enabled());
}

#[test]
fn test_config_parse() {
    // Note: Top-level keys must appear before any section headers in TOML
    let toml = r#"
# Top-level policies (must be before any [section] headers)
policies = [
    "file://./policies/security.pack",
    "https://example.com/policies/base.pack"
]

[project]
name = "testproject"
language = "rust"

[thresholds]
block = 0.8
flag = 0.5

[scan]
exclude = ["build/", "dist/"]
"#;

    let config: AphoriaConfig = toml::from_str(toml).expect("should parse");

    assert_eq!(config.project.name, Some("testproject".to_string()));
    assert_eq!(config.project.language, Some("rust".to_string()));
    assert_eq!(config.thresholds.block, 0.8);
    assert_eq!(config.thresholds.flag, 0.5);
    assert!(config.scan.exclude.contains(&"build/".to_string()));
    assert_eq!(config.policies.len(), 2);
    assert_eq!(config.policies[0], "file://./policies/security.pack");
}

#[test]
fn test_hosted_config_parse() {
    let toml = r#"
[project]
name = "billing-service"

[hosted]
url = "https://episteme.acme.corp"
project_id = "billing-api"
team_id = "platform-team"
sync_mode = "local-and-remote"
offline_fallback = "fail"
max_retries = 5
retry_delay_ms = 2000
api_key_env = "CUSTOM_API_KEY"
"#;

    let config: AphoriaConfig = toml::from_str(toml).expect("should parse");

    assert!(config.hosted.is_enabled());
    assert_eq!(config.hosted.url, Some("https://episteme.acme.corp".to_string()));
    assert_eq!(config.hosted.project_id, Some("billing-api".to_string()));
    assert_eq!(config.hosted.team_id, Some("platform-team".to_string()));
    assert_eq!(config.hosted.sync_mode, SyncMode::LocalAndRemote);
    assert_eq!(config.hosted.offline_fallback, OfflineFallback::Fail);
    assert_eq!(config.hosted.max_retries, 5);
    assert_eq!(config.hosted.retry_delay_ms, 2000);
    assert_eq!(config.hosted.api_key_env, "CUSTOM_API_KEY");
}

#[test]
fn test_hosted_config_defaults() {
    let toml = r#"
[hosted]
url = "https://episteme.acme.corp"
"#;

    let config: AphoriaConfig = toml::from_str(toml).expect("should parse");

    assert!(config.hosted.is_enabled());
    assert_eq!(config.hosted.url, Some("https://episteme.acme.corp".to_string()));
    assert_eq!(config.hosted.project_id, None);
    assert_eq!(config.hosted.team_id, None);
    assert_eq!(config.hosted.sync_mode, SyncMode::RemoteOnly);
    assert_eq!(config.hosted.offline_fallback, OfflineFallback::Skip);
    assert_eq!(config.hosted.max_retries, 3);
    assert_eq!(config.hosted.retry_delay_ms, 1000);
    assert_eq!(config.hosted.api_key_env, "APHORIA_API_KEY");
}

#[test]
fn test_hosted_not_enabled_without_url() {
    let config = AphoriaConfig::default();
    assert!(!config.hosted.is_enabled());

    let toml = r#"
[hosted]
project_id = "test"
"#;
    let config: AphoriaConfig = toml::from_str(toml).expect("should parse");
    assert!(!config.hosted.is_enabled());
}

// --- Extractor file loading tests ---

#[test]
fn test_from_file_loads_extractor_files() {
    let tmp = tempfile::tempdir().expect("create temp dir");
    let aphoria_dir = tmp.path().join(".aphoria");
    let extractors_dir = aphoria_dir.join("extractors");
    std::fs::create_dir_all(&extractors_dir).expect("create extractors dir");

    // Write a minimal config
    let config_path = aphoria_dir.join("config.toml");
    std::fs::write(
        &config_path,
        r#"
[project]
name = "test"
"#,
    )
    .expect("write config");

    // Write an extractor file with 2 declarative extractors
    std::fs::write(
        extractors_dir.join("security.toml"),
        r#"
[[extractors.declarative]]
name = "test_extractor_1"
description = "First test extractor"
languages = ["rust"]
pattern = "unsafe"
confidence = 0.9

[extractors.declarative.claim]
subject = "safety/unsafe_block"
predicate = "present"
value = true

[[extractors.declarative]]
name = "test_extractor_2"
description = "Second test extractor"
languages = ["go"]
pattern = "fmt\\.Println"
confidence = 1.0

[extractors.declarative.claim]
subject = "debug/println"
predicate = "present"
value = true
"#,
    )
    .expect("write extractor file");

    let config = AphoriaConfig::from_file(&config_path).expect("should load config");
    assert_eq!(config.extractors.declarative.len(), 2);
    assert_eq!(config.extractors.declarative[0].name, "test_extractor_1");
    assert_eq!(config.extractors.declarative[1].name, "test_extractor_2");
}

#[test]
fn test_from_file_merges_inline_and_file_extractors() {
    let tmp = tempfile::tempdir().expect("create temp dir");
    let aphoria_dir = tmp.path().join(".aphoria");
    let extractors_dir = aphoria_dir.join("extractors");
    std::fs::create_dir_all(&extractors_dir).expect("create extractors dir");

    // Config with one inline declarative extractor
    let config_path = aphoria_dir.join("config.toml");
    std::fs::write(
        &config_path,
        r#"
[project]
name = "test"

[[extractors.declarative]]
name = "inline_extractor"
languages = ["rust"]
pattern = "todo!"

[extractors.declarative.claim]
subject = "code/todo"
predicate = "present"
value = true
"#,
    )
    .expect("write config");

    // External extractor file
    std::fs::write(
        extractors_dir.join("extra.toml"),
        r#"
[[extractors.declarative]]
name = "file_extractor"
languages = ["python"]
pattern = "import os"

[extractors.declarative.claim]
subject = "imports/os"
predicate = "present"
value = true
"#,
    )
    .expect("write extractor file");

    let config = AphoriaConfig::from_file(&config_path).expect("should load config");
    assert_eq!(config.extractors.declarative.len(), 2);

    let names: Vec<&str> = config.extractors.declarative.iter().map(|e| e.name.as_str()).collect();
    assert!(names.contains(&"inline_extractor"));
    assert!(names.contains(&"file_extractor"));
}

#[test]
fn test_extractor_file_with_invalid_toml_warns_but_continues() {
    let tmp = tempfile::tempdir().expect("create temp dir");
    let aphoria_dir = tmp.path().join(".aphoria");
    let extractors_dir = aphoria_dir.join("extractors");
    std::fs::create_dir_all(&extractors_dir).expect("create extractors dir");

    let config_path = aphoria_dir.join("config.toml");
    std::fs::write(&config_path, "[project]\nname = \"test\"\n").expect("write config");

    // Write one invalid file
    std::fs::write(extractors_dir.join("bad.toml"), "this is not valid { toml [")
        .expect("write bad file");

    // Write one valid file
    std::fs::write(
        extractors_dir.join("good.toml"),
        r#"
[[extractors.declarative]]
name = "valid_one"
languages = ["rust"]
pattern = "unwrap"

[extractors.declarative.claim]
subject = "safety/unwrap"
predicate = "present"
value = true
"#,
    )
    .expect("write good file");

    let config = AphoriaConfig::from_file(&config_path).expect("should load despite bad file");
    assert_eq!(config.extractors.declarative.len(), 1);
    assert_eq!(config.extractors.declarative[0].name, "valid_one");
}

#[test]
fn test_extractor_file_non_toml_skipped() {
    let tmp = tempfile::tempdir().expect("create temp dir");
    let aphoria_dir = tmp.path().join(".aphoria");
    let extractors_dir = aphoria_dir.join("extractors");
    std::fs::create_dir_all(&extractors_dir).expect("create extractors dir");

    let config_path = aphoria_dir.join("config.toml");
    std::fs::write(&config_path, "[project]\nname = \"test\"\n").expect("write config");

    // Non-TOML files should be ignored
    std::fs::write(extractors_dir.join("README.md"), "# Extractors").expect("write md");
    std::fs::write(extractors_dir.join("notes.yaml"), "key: value").expect("write yaml");

    let config = AphoriaConfig::from_file(&config_path).expect("should load");
    assert!(config.extractors.declarative.is_empty());
}

#[test]
fn test_no_extractors_dir_is_fine() {
    let tmp = tempfile::tempdir().expect("create temp dir");
    let aphoria_dir = tmp.path().join(".aphoria");
    std::fs::create_dir_all(&aphoria_dir).expect("create aphoria dir");
    // No extractors/ subdirectory

    let config_path = aphoria_dir.join("config.toml");
    std::fs::write(&config_path, "[project]\nname = \"test\"\n").expect("write config");

    let config = AphoriaConfig::from_file(&config_path).expect("should load");
    assert!(config.extractors.declarative.is_empty());
}
