//! Compilation Tests for IdentitySpec -> IdentityPlan (IR)
//! Tests COMP-001 thru COMP-014

use std::collections::HashMap;
use cannon_common::spec::{
    IdentitySpec, BlockingStrategy, RuleSpec, SimilarityAlgorithm,
    SurvivorshipStrategy, Cardinality, SurvivorshipOverride,
    AdapterType, SamplingStrategy,
};
use cannon_core::compiler::SpecCompiler;

// Helper to get a minimal valid spec
fn minimal_spec() -> IdentitySpec {
    let yaml = r#"
api_version: "kanoniv/v2"
identity_version: "v1"
entity:
  name: customer
sources:
  - name: crm
    system: salesforce
    table: contacts
    id: contact_id
    attributes:
      email: email_field
rules:
  - name: email_match
    type: exact
    field: email
    weight: 1.0
decision:
  thresholds:
    match: 0.9
"#;
    IdentitySpec::from_yaml(yaml).unwrap()
}

// ============================================================================
// COMP-001: Minimal Spec Compilation
// ============================================================================
#[test]
fn comp_001_compile_minimal_spec() {
    let spec = minimal_spec();
    let plan = SpecCompiler::compile(&spec).expect("Minimal spec should compile");

    assert_eq!(plan.entity, "customer");
    assert_eq!(plan.identity_version, "v1");
    assert_eq!(plan.sources.len(), 1);
    assert_eq!(plan.sources[0].name, "crm");
    assert_eq!(plan.decision.match_threshold, 0.9);
    assert!(!plan.plan_hash.is_empty());
}

// ============================================================================
// COMP-002: Full-Featured Spec Compilation
// ============================================================================
#[test]
fn comp_002_compile_full_featured_spec() {
    let yaml = r#"
api_version: "kanoniv/v2"
identity_version: "v2"
entity:
  name: customer
sources:
  - name: crm
    system: salesforce
    table: contacts
    id: contact_id
    attributes:
      email: email_address
      name: full_name
  - name: erp
    system: sap
    table: customers
    id: cust_id
    attributes:
      email: email
blocking:
  strategy: composite
  keys:
    - [email]
rules:
  - name: email_exact
    type: exact
    field: email
    weight: 1.0
  - name: name_fuzzy
    type: similarity
    field: name
    algorithm: jaro_winkler
    threshold: 0.85
    weight: 0.8
survivorship:
  default: most_recent
  overrides:
    - field: email
      strategy: most_complete
relations:
  - name: household
    from_entity: customer
    to_entity: customer
    cardinality: many_to_many
    join_key: household_id
    propagate_match: true
decision:
  thresholds:
    match: 0.9
"#;
    let spec = IdentitySpec::from_yaml(yaml).unwrap();
    let plan = SpecCompiler::compile(&spec).expect("Full spec should compile");

    assert_eq!(plan.sources.len(), 2);
    assert_eq!(plan.blocking.strategy, BlockingStrategy::Composite);
    assert_eq!(plan.survivorship.default_strategy, SurvivorshipStrategy::MostRecent);
    assert_eq!(plan.relations.len(), 1);
    assert_eq!(plan.relations[0].name, "household");
    assert!(plan.relations[0].propagate_match);
}

// ============================================================================
// COMP-003: Blocking Strategy Compilation
// ============================================================================
#[test]
fn comp_003_compile_lsh_blocking() {
    let mut spec = minimal_spec();
    spec.blocking = Some(cannon_common::spec::BlockingSpec {
        strategy: BlockingStrategy::Lsh,
        keys: vec![vec!["email".to_string()]],
        fallback: None,
    });

    let plan = SpecCompiler::compile(&spec).expect("LSH spec should compile");
    assert_eq!(plan.blocking.strategy, BlockingStrategy::Lsh);
    assert_eq!(plan.blocking.keys.len(), 1);
}

// ============================================================================
// COMP-004: Rule Compilation
// ============================================================================
#[test]
fn comp_004_compile_multiple_rules() {
    let mut spec = minimal_spec();
    spec.rules = vec![
        RuleSpec::Exact {
            name: "r1".to_string(),
            field: "email".to_string(),
            weight: 1.0,
            options: None,
        },
        RuleSpec::Similarity {
            name: "r2".to_string(),
            field: "name".to_string(),
            algorithm: SimilarityAlgorithm::Levenshtein,
            threshold: 0.8,
            weight: 0.5,
            locale: None,
            options: None,
            normalizer: None,
        },
    ];

    let _plan = SpecCompiler::compile(&spec).expect("Multi-rule spec should compile");
}

// ============================================================================
// COMP-005: Survivorship Policy Compilation
// ============================================================================
#[test]
fn comp_005_compile_survivorship_policy() {
    let mut spec = minimal_spec();
    spec.survivorship = Some(cannon_common::spec::SurvivorshipSpec {
        default: SurvivorshipStrategy::MostRecent,
        overrides: vec![
            SurvivorshipOverride {
                field: "email".to_string(),
                strategy: SurvivorshipStrategy::MostComplete,
                priority: vec![],
                function: None,
            }
        ],
    });

    let plan = SpecCompiler::compile(&spec).expect("Survivorship spec should compile");
    assert_eq!(plan.survivorship.default_strategy, SurvivorshipStrategy::MostRecent);
    assert!(plan.survivorship.field_policies.contains_key("email"));
}

// ============================================================================
// COMP-006: Relation Compilation
// ============================================================================
#[test]
fn comp_006_compile_relations() {
    let mut spec = minimal_spec();
    spec.relations = Some(vec![
        cannon_common::spec::RelationSpec {
            name: "household".to_string(),
            from_entity: "customer".to_string(),
            to_entity: "customer".to_string(),
            join_key: "household_id".to_string(),
            cardinality: Cardinality::ManyToMany,
            propagate_match: true,
        }
    ]);

    let plan = SpecCompiler::compile(&spec).expect("Relation spec should compile");
    assert_eq!(plan.relations.len(), 1);
    assert_eq!(plan.relations[0].name, "household");
    assert!(plan.relations[0].propagate_match);
}

// ============================================================================
// COMP-007: Reference Identifier Compilation
// ============================================================================
#[test]
fn comp_007_compile_reference_identifiers() {
    let mut spec = minimal_spec();
    spec.reference_identifiers = Some(vec![
        cannon_common::spec::ReferenceIdentifierSpec {
            name: "ssn".to_string(),
            field: "ssn".to_string(),
            id_type: cannon_common::spec::ReferenceIdType::External,
            authority: None,
            match_weight: 1.0,
            conditions: vec![],
        }
    ]);

    let plan = SpecCompiler::compile(&spec).expect("Ref Identifier spec should compile");
    assert_eq!(plan.reference_identifiers.len(), 1);
    assert_eq!(plan.reference_identifiers[0].name, "ssn");
}

// ============================================================================
// COMP-008: Decision Threshold Compilation
// ============================================================================
#[test]
fn comp_008_compile_decision_thresholds() {
    let mut spec = minimal_spec();
    spec.decision.thresholds.match_threshold = 0.95;
    spec.decision.thresholds.review = Some(0.85);

    let plan = SpecCompiler::compile(&spec).expect("Decision spec should compile");
    assert_eq!(plan.decision.match_threshold, 0.95);
    assert_eq!(plan.decision.review_threshold, Some(0.85));
}

// ============================================================================
// COMP-009: Adapter-First Source Compilation
// ============================================================================
#[test]
fn comp_009_compile_adapter_first_source() {
    let yaml = r#"
api_version: "kanoniv/v2"
identity_version: "v1"
entity:
  name: customer
sources:
  - name: warehouse
    adapter: snowflake
    location: analytics.public.customers
    primary_key: customer_id
    attributes:
      email: email_address
      name: full_name
rules:
  - name: email_match
    type: exact
    field: email
    weight: 1.0
decision:
  thresholds:
    match: 0.9
"#;
    let spec = IdentitySpec::from_yaml(yaml).unwrap();
    let plan = SpecCompiler::compile(&spec).expect("Adapter-first spec should compile");

    assert_eq!(plan.sources.len(), 1);
    assert_eq!(plan.sources[0].adapter, "snowflake");
    assert_eq!(plan.sources[0].location, "analytics.public.customers");
    assert_eq!(plan.sources[0].id_field, "customer_id");
}

// ============================================================================
// COMP-010: Legacy Source Backward Compat Compilation
// ============================================================================
#[test]
fn comp_010_compile_legacy_source_backward_compat() {
    let spec = minimal_spec();
    let plan = SpecCompiler::compile(&spec).expect("Legacy spec should compile");

    assert_eq!(plan.sources[0].adapter, "salesforce");
    assert_eq!(plan.sources[0].location, "contacts");
    assert_eq!(plan.sources[0].id_field, "contact_id");
}

// ============================================================================
// COMP-011: PII Fields Extracted from Source Schema
// ============================================================================
#[test]
fn comp_011_compile_pii_fields_from_schema() {
    let yaml = r#"
api_version: "kanoniv/v2"
identity_version: "v1"
entity:
  name: customer
sources:
  - name: crm
    adapter: postgres
    location: public.contacts
    primary_key: contact_id
    schema:
      ssn:
        type: string
        pii: true
      email:
        type: string
        pii: true
      name:
        type: string
        nullable: true
      age:
        type: integer
    attributes:
      ssn: ssn_field
      email: email_address
      name: full_name
rules:
  - name: email_match
    type: exact
    field: email
    weight: 1.0
decision:
  thresholds:
    match: 0.9
"#;
    let spec = IdentitySpec::from_yaml(yaml).unwrap();
    let plan = SpecCompiler::compile(&spec).expect("PII schema spec should compile");

    let source = &plan.sources[0];
    let mut pii = source.pii_fields.clone();
    pii.sort();
    assert_eq!(pii, vec!["email", "ssn"]);

    // Schema should be compiled
    let schema = source.schema.as_ref().expect("Schema should be compiled");
    assert_eq!(schema.len(), 4);
    assert!(schema["ssn"].pii);
    assert!(schema["email"].pii);
    assert!(!schema["name"].pii);
    assert!(schema["name"].nullable);
    assert_eq!(schema["age"].field_type, "integer");
}

// ============================================================================
// COMP-012: Sampling Compilation
// ============================================================================
#[test]
fn comp_012_compile_sampling() {
    let yaml = r#"
api_version: "kanoniv/v2"
identity_version: "v1"
entity:
  name: customer
sources:
  - name: large_source
    adapter: bigquery
    location: project.dataset.table
    primary_key: id
    sampling:
      strategy: random
      rate: 0.1
    attributes:
      email: email
rules:
  - name: email_match
    type: exact
    field: email
    weight: 1.0
decision:
  thresholds:
    match: 0.9
"#;
    let spec = IdentitySpec::from_yaml(yaml).unwrap();
    let plan = SpecCompiler::compile(&spec).expect("Sampling spec should compile");

    let sampling = plan.sources[0].sampling.as_ref().expect("Sampling should be compiled");
    assert_eq!(sampling.strategy, "random");
    assert!((sampling.rate - 0.1).abs() < f64::EPSILON);
}

// ============================================================================
// COMP-013: Tags Compilation
// ============================================================================
#[test]
fn comp_013_compile_tags() {
    let yaml = r#"
api_version: "kanoniv/v2"
identity_version: "v1"
entity:
  name: customer
sources:
  - name: crm
    adapter: postgres
    location: public.contacts
    primary_key: id
    tags:
      - production
      - pii-source
      - gdpr-compliant
    attributes:
      email: email
rules:
  - name: email_match
    type: exact
    field: email
    weight: 1.0
decision:
  thresholds:
    match: 0.9
"#;
    let spec = IdentitySpec::from_yaml(yaml).unwrap();
    let plan = SpecCompiler::compile(&spec).expect("Tags spec should compile");

    assert_eq!(plan.sources[0].tags, vec!["production", "pii-source", "gdpr-compliant"]);
}

// ============================================================================
// COMP-014: Governance Compilation
// ============================================================================
#[test]
fn comp_014_compile_governance() {
    let yaml = r#"
api_version: "kanoniv/v2"
identity_version: "v1"
entity:
  name: customer
sources:
  - name: crm
    adapter: postgres
    location: public.contacts
    primary_key: id
    tags:
      - production
    attributes:
      email: email
rules:
  - name: email_match
    type: exact
    field: email
    weight: 1.0
decision:
  thresholds:
    match: 0.9
governance:
  require_freshness: true
  require_schema: true
  required_tags:
    - production
"#;
    let spec = IdentitySpec::from_yaml(yaml).unwrap();
    let plan = SpecCompiler::compile(&spec).expect("Governance spec should compile");

    let gov = plan.governance.as_ref().expect("Governance should be compiled");
    assert!(gov.require_freshness);
    assert!(gov.require_schema);
    assert_eq!(gov.required_tags, vec!["production"]);
}
