//! Tests for the Episteme integration module.

use stemedb_core::types::ObjectValue;

use super::*;
use crate::types::ConflictingSource;

// ==========================================================================
// ConceptIndex::make_key tests
// ==========================================================================

#[test]
fn test_make_key_rfc() {
    let key = ConceptIndex::make_key("rfc://5246/tls/cert_verification", "enabled");
    assert_eq!(key, Some("tls/cert_verification::enabled".to_string()));
}

#[test]
fn test_make_key_code() {
    let key = ConceptIndex::make_key("code://rust/myapp/client/tls/cert_verification", "enabled");
    assert_eq!(key, Some("tls/cert_verification::enabled".to_string()));
}

#[test]
fn test_make_key_owasp() {
    let key = ConceptIndex::make_key("owasp://secrets/api_key", "storage_method");
    assert_eq!(key, Some("secrets/api_key::storage_method".to_string()));
}

#[test]
fn test_make_key_single_segment_returns_none() {
    // Only one segment after scheme - cannot form tail pair
    let key = ConceptIndex::make_key("scheme://single", "predicate");
    assert_eq!(key, None);
}

#[test]
fn test_make_key_no_scheme() {
    // No "://" - whole string is path
    let key = ConceptIndex::make_key("tls/cert_verification", "enabled");
    assert_eq!(key, Some("tls/cert_verification::enabled".to_string()));
}

#[test]
fn test_make_key_empty_segments() {
    // Double slashes should be filtered out
    let key = ConceptIndex::make_key("rfc://5246//tls//cert_verification", "enabled");
    assert_eq!(key, Some("tls/cert_verification::enabled".to_string()));
}

// ==========================================================================
// ConceptIndex::lookup tests
// ==========================================================================

#[test]
fn test_lookup_matches_across_schemes() {
    let key = crate::bridge::generate_signing_key();
    let corpus = create_authoritative_corpus(&key);
    let index = ConceptIndex::build(&corpus);

    // Code claim should find RFC assertion
    let matches = index.lookup("code://rust/myapp/tls/cert_verification", "enabled");
    assert!(matches.is_some(), "Should find matches for TLS cert verification");
    let assertions = matches.expect("matches should exist");
    assert!(!assertions.is_empty(), "Should have at least one matching assertion");
    assert!(
        assertions.iter().any(|a| a.subject.contains("rfc://") || a.subject.contains("owasp://")),
        "Matches should include authoritative sources"
    );
}

#[test]
fn test_lookup_predicate_must_match() {
    let key = crate::bridge::generate_signing_key();
    let corpus = create_authoritative_corpus(&key);
    let index = ConceptIndex::build(&corpus);

    // Same path but wrong predicate should not match
    let matches = index.lookup("code://rust/myapp/tls/cert_verification", "wrong_predicate");
    assert!(matches.is_none(), "Wrong predicate should not match");
}

#[test]
fn test_no_match_for_uncovered_concept() {
    let key = crate::bridge::generate_signing_key();
    let corpus = create_authoritative_corpus(&key);
    let index = ConceptIndex::build(&corpus);

    // Concept not in authoritative corpus
    let matches = index.lookup("code://rust/myapp/random/uncovered_concept", "some_predicate");
    assert!(matches.is_none(), "Uncovered concept should not match");
}

#[test]
fn test_lookup_jwt_audience() {
    let key = crate::bridge::generate_signing_key();
    let corpus = create_authoritative_corpus(&key);
    let index = ConceptIndex::build(&corpus);

    // JWT audience validation
    let matches = index.lookup("code://rust/myapp/jwt/audience_validation", "enabled");
    assert!(matches.is_some(), "Should find JWT audience validation");
}

// ==========================================================================
// Conflict score tests
// ==========================================================================

#[test]
fn test_conflict_score_tier0_vs_tier3() {
    let conflicts = vec![ConflictingSource {
        path: "rfc://5246/tls/cert_verification".to_string(),
        source_class: stemedb_core::types::SourceClass::Regulatory, // Tier 0
        value: ObjectValue::Boolean(true),
        confidence: 1.0,
        rfc_citation: Some("RFC 5246".to_string()),
        policy_source: None,
    }];

    let score = compute_conflict_score(&conflicts, 1.0);

    // Tier 0 (1.0 weight) vs Tier 3 (0.5 weight) should produce high score
    assert!(score >= 0.7, "Expected high conflict score, got {}", score);
}

#[test]
fn test_conflict_score_tier1_vs_tier3() {
    let conflicts = vec![ConflictingSource {
        path: "owasp://transport_layer/tls".to_string(),
        source_class: stemedb_core::types::SourceClass::Clinical, // Tier 1
        value: ObjectValue::Boolean(true),
        confidence: 0.95,
        rfc_citation: Some("OWASP A05:2021".to_string()),
        policy_source: None,
    }];

    let score = compute_conflict_score(&conflicts, 1.0);

    // Should still be above FLAG threshold
    assert!(score >= 0.4, "Expected medium conflict score, got {}", score);
}

#[test]
fn test_authoritative_corpus_creation() {
    let key = crate::bridge::generate_signing_key();
    let corpus = create_authoritative_corpus(&key);

    // Should have at least 10 authoritative assertions
    assert!(corpus.len() >= 10, "Expected at least 10 assertions, got {}", corpus.len());

    // Check that TLS and JWT assertions exist
    assert!(corpus.iter().any(|a| a.subject.contains("tls")));
    assert!(corpus.iter().any(|a| a.subject.contains("jwt")));
}

// ==========================================================================
// Auto-alias creation tests (Phase 2A.3)
// ==========================================================================

#[tokio::test]
async fn test_auto_alias_creation_on_conflict() {
    use crate::types::ExtractedClaim;
    use stemedb_storage::AliasStore;

    let temp_dir =
        tempfile::Builder::new().prefix("aphoria_alias_test").tempdir().expect("create temp dir");

    let mut config = crate::config::AphoriaConfig::default();
    config.episteme.data_dir = temp_dir.path().join(".aphoria").join("db");
    config.aliases.auto_create_aliases = true; // Explicitly enable

    // Create .aphoria directory for the agent key
    let aphoria_dir = temp_dir.path().join(".aphoria");
    std::fs::create_dir_all(&aphoria_dir).expect("create .aphoria dir");

    // Open LocalEpisteme
    let mut episteme = LocalEpisteme::open(&config, temp_dir.path()).await.expect("open");

    // Create authoritative corpus and index
    let signing_key = crate::bridge::load_or_generate_key(temp_dir.path()).expect("load key");
    let corpus = create_authoritative_corpus(&signing_key);
    let index = ConceptIndex::build(&corpus);

    // Create a claim that will conflict with the authoritative corpus
    let claim = ExtractedClaim {
        concept_path: "code://rust/myapp/tls/cert_verification".to_string(),
        predicate: "enabled".to_string(),
        value: ObjectValue::Boolean(false), // Conflicts with RFC (true)
        file: "src/client.rs".to_string(),
        line: 42,
        matched_text: "danger_accept_invalid_certs(true)".to_string(),
        confidence: 1.0,
        description: "TLS verification disabled".to_string(),
    };

    // Run check_conflicts
    let conflicts =
        episteme.check_conflicts(&[claim], &config, &index).await.expect("check conflicts");

    // Assert: conflict was detected
    assert!(!conflicts.is_empty(), "Should have detected a conflict");

    // Assert: alias was created
    let canonical = episteme
        .alias_store()
        .get_canonical("code://rust/myapp/tls/cert_verification")
        .await
        .expect("get canonical");

    assert!(canonical.is_some(), "Alias should have been auto-created for code path");

    let canonical_path = canonical.expect("canonical exists");
    assert!(
        canonical_path.scheme == "rfc" || canonical_path.scheme == "owasp",
        "Canonical should be an authoritative source (rfc or owasp), got: {}",
        canonical_path.scheme
    );

    episteme.shutdown().await;
}

#[tokio::test]
async fn test_auto_alias_not_created_when_disabled() {
    use crate::types::ExtractedClaim;
    use stemedb_storage::AliasStore;

    let temp_dir = tempfile::Builder::new()
        .prefix("aphoria_alias_disabled")
        .tempdir()
        .expect("create temp dir");

    let mut config = crate::config::AphoriaConfig::default();
    config.episteme.data_dir = temp_dir.path().join(".aphoria").join("db");
    config.aliases.auto_create_aliases = false; // Explicitly disable

    let aphoria_dir = temp_dir.path().join(".aphoria");
    std::fs::create_dir_all(&aphoria_dir).expect("create .aphoria dir");

    let mut episteme = LocalEpisteme::open(&config, temp_dir.path()).await.expect("open");

    let signing_key = crate::bridge::load_or_generate_key(temp_dir.path()).expect("load key");
    let corpus = create_authoritative_corpus(&signing_key);
    let index = ConceptIndex::build(&corpus);

    let claim = ExtractedClaim {
        concept_path: "code://rust/myapp/tls/cert_verification".to_string(),
        predicate: "enabled".to_string(),
        value: ObjectValue::Boolean(false),
        file: "src/client.rs".to_string(),
        line: 42,
        matched_text: "danger_accept_invalid_certs(true)".to_string(),
        confidence: 1.0,
        description: "TLS verification disabled".to_string(),
    };

    let conflicts =
        episteme.check_conflicts(&[claim], &config, &index).await.expect("check conflicts");

    // Conflict should still be detected
    assert!(!conflicts.is_empty(), "Should have detected a conflict");

    // But alias should NOT have been created
    let canonical = episteme
        .alias_store()
        .get_canonical("code://rust/myapp/tls/cert_verification")
        .await
        .expect("get canonical");

    assert!(canonical.is_none(), "Alias should NOT be created when auto_create_aliases is false");

    episteme.shutdown().await;
}

#[tokio::test]
async fn test_auto_alias_uses_auto_detected_origin() {
    use crate::types::ExtractedClaim;
    use stemedb_storage::AliasStore;

    let temp_dir =
        tempfile::Builder::new().prefix("aphoria_alias_origin").tempdir().expect("create temp dir");

    let mut config = crate::config::AphoriaConfig::default();
    config.episteme.data_dir = temp_dir.path().join(".aphoria").join("db");
    config.aliases.auto_create_aliases = true;

    let aphoria_dir = temp_dir.path().join(".aphoria");
    std::fs::create_dir_all(&aphoria_dir).expect("create .aphoria dir");

    let mut episteme = LocalEpisteme::open(&config, temp_dir.path()).await.expect("open");

    let signing_key = crate::bridge::load_or_generate_key(temp_dir.path()).expect("load key");
    let corpus = create_authoritative_corpus(&signing_key);
    let index = ConceptIndex::build(&corpus);

    let claim = ExtractedClaim {
        concept_path: "code://rust/myapp/jwt/audience_validation".to_string(),
        predicate: "enabled".to_string(),
        value: ObjectValue::Boolean(false),
        file: "src/auth.rs".to_string(),
        line: 100,
        matched_text: "validate_aud = false".to_string(),
        confidence: 1.0,
        description: "JWT audience validation disabled".to_string(),
    };

    let _conflicts =
        episteme.check_conflicts(&[claim], &config, &index).await.expect("check conflicts");

    // Verify alias was created (we can check it exists)
    let canonical = episteme
        .alias_store()
        .get_canonical("code://rust/myapp/jwt/audience_validation")
        .await
        .expect("get canonical");

    assert!(canonical.is_some(), "Alias should have been created for JWT path");

    // The AliasOrigin is stored internally; we verified it's set to AutoDetected
    // in the create_alias_if_new implementation. The existence of the alias
    // confirms the code path was executed.

    episteme.shutdown().await;
}

#[tokio::test]
async fn test_auto_alias_idempotent() {
    use crate::types::ExtractedClaim;
    use stemedb_storage::AliasStore;

    let temp_dir = tempfile::Builder::new()
        .prefix("aphoria_alias_idempotent")
        .tempdir()
        .expect("create temp dir");

    let mut config = crate::config::AphoriaConfig::default();
    config.episteme.data_dir = temp_dir.path().join(".aphoria").join("db");
    config.aliases.auto_create_aliases = true;

    let aphoria_dir = temp_dir.path().join(".aphoria");
    std::fs::create_dir_all(&aphoria_dir).expect("create .aphoria dir");

    let mut episteme = LocalEpisteme::open(&config, temp_dir.path()).await.expect("open");

    let signing_key = crate::bridge::load_or_generate_key(temp_dir.path()).expect("load key");
    let corpus = create_authoritative_corpus(&signing_key);
    let index = ConceptIndex::build(&corpus);

    let claim = ExtractedClaim {
        concept_path: "code://rust/myapp/tls/cert_verification".to_string(),
        predicate: "enabled".to_string(),
        value: ObjectValue::Boolean(false),
        file: "src/client.rs".to_string(),
        line: 42,
        matched_text: "danger_accept_invalid_certs(true)".to_string(),
        confidence: 1.0,
        description: "TLS verification disabled".to_string(),
    };

    // Run check_conflicts twice
    let _conflicts1 = episteme
        .check_conflicts(std::slice::from_ref(&claim), &config, &index)
        .await
        .expect("check conflicts 1");

    let _conflicts2 =
        episteme.check_conflicts(&[claim], &config, &index).await.expect("check conflicts 2");

    // List all aliases - should only have one entry for this code path
    let all_aliases = episteme.alias_store().list_all_aliases().await.expect("list aliases");

    let tls_aliases: Vec<_> =
        all_aliases.iter().filter(|(alias, _)| alias.contains("tls/cert_verification")).collect();

    // Should have exactly one TLS alias (the code path → RFC)
    assert!(
        tls_aliases.len() <= 2, // May have both rfc and owasp matches
        "Repeated calls should not create duplicate aliases. Found: {:?}",
        tls_aliases
    );

    episteme.shutdown().await;
}

// ==========================================================================
// Observation write-back tests (Phase 4A)
// ==========================================================================

#[tokio::test]
async fn test_ingest_observations_creates_tier4_assertions() {
    use crate::types::ExtractedClaim;

    let temp_dir =
        tempfile::Builder::new().prefix("aphoria_observations").tempdir().expect("create temp dir");

    let mut config = crate::config::AphoriaConfig::default();
    config.episteme.data_dir = temp_dir.path().join(".aphoria").join("db");

    let aphoria_dir = temp_dir.path().join(".aphoria");
    std::fs::create_dir_all(&aphoria_dir).expect("create .aphoria dir");

    let mut episteme = LocalEpisteme::open(&config, temp_dir.path()).await.expect("open");

    // Create claims that would NOT conflict with authority
    let observations = vec![
        ExtractedClaim {
            concept_path: "code://rust/myapp/logging/level".to_string(),
            predicate: "value".to_string(),
            value: ObjectValue::Text("info".to_string()),
            file: "src/config.rs".to_string(),
            line: 10,
            matched_text: "log_level = \"info\"".to_string(),
            confidence: 0.9,
            description: "Logging level set to info".to_string(),
        },
        ExtractedClaim {
            concept_path: "code://rust/myapp/db/pool_size".to_string(),
            predicate: "value".to_string(),
            value: ObjectValue::Number(10.0),
            file: "src/db.rs".to_string(),
            line: 25,
            matched_text: "pool_size = 10".to_string(),
            confidence: 1.0,
            description: "Database pool size".to_string(),
        },
    ];

    // Ingest observations
    let count = episteme.ingest_observations(&observations).await.expect("ingest observations");
    assert_eq!(count, 2, "Should have ingested 2 observations");

    // The observations are written to WAL and store.
    // We verify the count returned is correct - the internal storage
    // details are verified by unit tests in bridge.rs.

    episteme.shutdown().await;
}

#[tokio::test]
async fn test_ingest_observations_empty_list() {
    let temp_dir =
        tempfile::Builder::new().prefix("aphoria_obs_empty").tempdir().expect("create temp dir");

    let mut config = crate::config::AphoriaConfig::default();
    config.episteme.data_dir = temp_dir.path().join(".aphoria").join("db");

    let aphoria_dir = temp_dir.path().join(".aphoria");
    std::fs::create_dir_all(&aphoria_dir).expect("create .aphoria dir");

    let mut episteme = LocalEpisteme::open(&config, temp_dir.path()).await.expect("open");

    // Ingest empty list
    let count = episteme.ingest_observations(&[]).await.expect("ingest observations");
    assert_eq!(count, 0, "Should have ingested 0 observations for empty list");

    episteme.shutdown().await;
}
