//! Integration tests for the PII detection and classification module.
//!
//! Tests cover schema classification, query PII checking, dialect-specific
//! masking suggestions, and interactions with the ecommerce schema fixture.

use altimate_core::pii::{PiiClassification, PiiRiskLevel};
use altimate_core::pii::{check_query_pii, classify_column, classify_schema, suggest_masking};
use altimate_core::schema::types::{ColumnDef, SchemaDefinition, SqlDialect, TableDef};
use std::collections::HashMap;

// ── Helpers ──

fn ecommerce_schema() -> SchemaDefinition {
    SchemaDefinition::from_yaml(include_str!(
        "../../../tests/fixtures/schemas/ecommerce.yaml"
    ))
    .expect("Failed to load ecommerce schema")
}

fn make_col(name: &str, data_type: &str, tags: Vec<&str>) -> ColumnDef {
    ColumnDef {
        name: name.to_string(),
        data_type: data_type.to_string(),
        nullable: true,
        description: None,
        tags: tags.into_iter().map(|s| s.to_string()).collect(),
        synonyms: vec![],

        statistics: None,
    }
}

fn pii_heavy_schema() -> SchemaDefinition {
    let mut tables = HashMap::new();
    tables.insert(
        "customers".to_string(),
        TableDef {
            description: Some("Customer records with PII".to_string()),
            database: None,
            schema: None,
            columns: vec![
                make_col("id", "INT", vec![]),
                make_col("email", "VARCHAR", vec!["pii"]),
                make_col("phone_number", "VARCHAR", vec![]),
                make_col("ssn", "VARCHAR", vec![]),
                make_col("first_name", "VARCHAR", vec![]),
                make_col("last_name", "VARCHAR", vec![]),
                make_col("address", "VARCHAR", vec![]),
                make_col("salary", "DECIMAL(10,2)", vec![]),
                make_col("dob", "DATE", vec![]),
                make_col("ip_address", "VARCHAR", vec![]),
                make_col("credit_card", "VARCHAR", vec![]),
                make_col("passport", "VARCHAR", vec![]),
                make_col("created_at", "TIMESTAMP", vec![]),
                make_col("status", "VARCHAR", vec![]),
            ],
            primary_key: Some(vec!["id".to_string()]),
            foreign_keys: vec![],
            statistics: None,

            clustering_keys: vec![],
        },
    );
    tables.insert(
        "orders".to_string(),
        TableDef {
            description: None,
            database: None,
            schema: None,
            columns: vec![
                make_col("id", "INT", vec![]),
                make_col("customer_id", "INT", vec![]),
                make_col("total", "DECIMAL(10,2)", vec![]),
            ],
            primary_key: Some(vec!["id".to_string()]),
            foreign_keys: vec![],
            statistics: None,

            clustering_keys: vec![],
        },
    );
    SchemaDefinition {
        version: "1".to_string(),
        dialect: SqlDialect::Generic,
        tables,
        database: None,
        schema_name: None,
        glossary: None,
    }
}

// ═══════════════════════════════════════════════════════════════════════
// classify_schema — ecommerce fixture
// ═══════════════════════════════════════════════════════════════════════

#[test]
fn pii_classify_ecommerce_schema_detects_email() {
    let schema = ecommerce_schema();
    let report = classify_schema(&schema);
    let email_col = report.columns.iter().find(|c| c.column == "email");
    assert!(email_col.is_some(), "should find email column");
    assert_ne!(
        email_col.unwrap().classification,
        PiiClassification::None,
        "email should be PII"
    );
}

#[test]
fn pii_classify_ecommerce_has_pii_count() {
    let schema = ecommerce_schema();
    let report = classify_schema(&schema);
    assert!(report.pii_count > 0, "ecommerce schema has PII columns");
    assert!(report.total_columns > report.pii_count);
}

#[test]
fn pii_classify_ecommerce_non_pii_columns() {
    let schema = ecommerce_schema();
    let report = classify_schema(&schema);
    let status_col = report
        .columns
        .iter()
        .find(|c| c.column == "status" && c.table == "users");
    assert!(status_col.is_some());
    assert_eq!(status_col.unwrap().classification, PiiClassification::None);
}

// ═══════════════════════════════════════════════════════════════════════
// classify_schema — PII-heavy schema
// ═══════════════════════════════════════════════════════════════════════

#[test]
fn pii_classify_heavy_schema_counts() {
    let schema = pii_heavy_schema();
    let report = classify_schema(&schema);
    // email, phone, ssn, first_name, last_name, address, salary, dob,
    // ip_address, credit_card, passport — at least 10 PII columns
    assert!(report.pii_count >= 10);
}

#[test]
fn pii_classify_heavy_schema_risk_high() {
    let schema = pii_heavy_schema();
    let report = classify_schema(&schema);
    assert_eq!(report.risk_level, PiiRiskLevel::High);
}

#[test]
fn pii_classify_heavy_schema_ssn_classified() {
    let schema = pii_heavy_schema();
    let report = classify_schema(&schema);
    let ssn = report
        .columns
        .iter()
        .find(|c| c.column == "ssn")
        .expect("should find ssn column");
    assert_eq!(ssn.classification, PiiClassification::SSN);
}

#[test]
fn pii_classify_heavy_schema_credit_card_classified() {
    let schema = pii_heavy_schema();
    let report = classify_schema(&schema);
    let cc = report
        .columns
        .iter()
        .find(|c| c.column == "credit_card")
        .expect("should find credit_card");
    assert_eq!(cc.classification, PiiClassification::Financial);
}

#[test]
fn pii_classify_heavy_schema_passport_classified() {
    let schema = pii_heavy_schema();
    let report = classify_schema(&schema);
    let passport = report
        .columns
        .iter()
        .find(|c| c.column == "passport")
        .expect("should find passport");
    assert_eq!(
        passport.classification,
        PiiClassification::Custom("passport".to_string())
    );
}

#[test]
fn pii_classify_schema_no_pii() {
    let mut tables = HashMap::new();
    tables.insert(
        "metrics".to_string(),
        TableDef {
            description: None,
            database: None,
            schema: None,
            columns: vec![
                make_col("id", "INT", vec![]),
                make_col("value", "FLOAT", vec![]),
                make_col("timestamp", "TIMESTAMP", vec![]),
            ],
            primary_key: None,
            foreign_keys: vec![],
            statistics: None,

            clustering_keys: vec![],
        },
    );
    let schema = SchemaDefinition {
        version: "1".to_string(),
        dialect: SqlDialect::Generic,
        tables,
        database: None,
        schema_name: None,
        glossary: None,
    };
    let report = classify_schema(&schema);
    assert_eq!(report.pii_count, 0);
    assert_eq!(report.risk_level, PiiRiskLevel::None);
}

// ═══════════════════════════════════════════════════════════════════════
// check_query_pii — query-level PII detection
// ═══════════════════════════════════════════════════════════════════════

#[test]
fn pii_check_query_accesses_email() {
    let schema = pii_heavy_schema();
    let result = check_query_pii("SELECT email FROM customers", &schema);
    assert!(result.accesses_pii);
    assert!(!result.pii_columns.is_empty());
}

#[test]
fn pii_check_query_no_pii_columns() {
    let schema = pii_heavy_schema();
    let result = check_query_pii("SELECT id, status FROM customers", &schema);
    assert!(!result.accesses_pii);
}

#[test]
fn pii_check_query_ssn_high_risk() {
    let schema = pii_heavy_schema();
    let result = check_query_pii("SELECT ssn FROM customers", &schema);
    assert_eq!(result.risk_level, PiiRiskLevel::High);
}

#[test]
fn pii_check_query_multiple_pii_columns() {
    let schema = pii_heavy_schema();
    let result = check_query_pii("SELECT email, ssn, credit_card FROM customers", &schema);
    assert!(result.pii_columns.len() >= 3);
    assert_eq!(result.risk_level, PiiRiskLevel::High);
}

#[test]
fn pii_check_query_cross_table_join() {
    let schema = pii_heavy_schema();
    let result = check_query_pii(
        "SELECT c.email, o.total FROM customers c JOIN orders o ON c.id = o.customer_id",
        &schema,
    );
    assert!(result.accesses_pii);
    let has_email = result.pii_columns.iter().any(|c| c.column == "email");
    assert!(has_email);
}

#[test]
fn pii_check_query_where_on_pii() {
    let schema = pii_heavy_schema();
    let result = check_query_pii(
        "SELECT id FROM customers WHERE email = 'test@test.com'",
        &schema,
    );
    assert!(result.accesses_pii);
}

#[test]
fn pii_check_query_suggests_masking() {
    let schema = pii_heavy_schema();
    let result = check_query_pii("SELECT email FROM customers", &schema);
    assert!(!result.suggested_alternatives.is_empty());
}

#[test]
fn pii_check_query_unrelated_table_no_pii() {
    let schema = pii_heavy_schema();
    let result = check_query_pii("SELECT id, total FROM orders", &schema);
    assert!(!result.accesses_pii);
}

// ═══════════════════════════════════════════════════════════════════════
// classify_column — individual column classification
// ═══════════════════════════════════════════════════════════════════════

#[test]
fn pii_classify_column_tag_priority() {
    let col = make_col("random_name", "VARCHAR", vec!["pii_ssn"]);
    let result = classify_column("t", &col, SqlDialect::Generic);
    assert_eq!(result.classification, PiiClassification::SSN);
    assert_eq!(result.detection_method, "tag");
}

#[test]
fn pii_classify_column_name_pattern() {
    let col = make_col("email_address", "VARCHAR", vec![]);
    let result = classify_column("t", &col, SqlDialect::Generic);
    assert_eq!(result.classification, PiiClassification::Email);
    assert_eq!(result.detection_method, "name_pattern");
}

#[test]
fn pii_classify_column_non_pii() {
    let col = make_col("created_at", "TIMESTAMP", vec![]);
    let result = classify_column("t", &col, SqlDialect::Generic);
    assert_eq!(result.classification, PiiClassification::None);
}

// ═══════════════════════════════════════════════════════════════════════
// suggest_masking — dialect-specific masking
// ═══════════════════════════════════════════════════════════════════════

#[test]
fn pii_masking_snowflake_email() {
    let col = make_col("email", "VARCHAR", vec![]);
    let masking = suggest_masking(&col, SqlDialect::Snowflake).expect("should suggest");
    assert!(masking.contains("SHA2"));
}

#[test]
fn pii_masking_bigquery_email() {
    let col = make_col("email", "VARCHAR", vec![]);
    let masking = suggest_masking(&col, SqlDialect::BigQuery).expect("should suggest");
    assert!(masking.contains("SHA256"));
}

#[test]
fn pii_masking_databricks_email() {
    let col = make_col("email", "VARCHAR", vec![]);
    let masking = suggest_masking(&col, SqlDialect::Databricks).expect("should suggest");
    assert!(masking.contains("sha2"));
}

#[test]
fn pii_masking_postgres_email() {
    let col = make_col("email", "VARCHAR", vec![]);
    let masking = suggest_masking(&col, SqlDialect::Postgres).expect("should suggest");
    assert!(masking.contains("sha256"));
}

#[test]
fn pii_masking_mysql_email() {
    let col = make_col("email", "VARCHAR", vec![]);
    let masking = suggest_masking(&col, SqlDialect::MySQL).expect("should suggest");
    assert!(masking.contains("SHA2"));
}

#[test]
fn pii_masking_redshift_email() {
    let col = make_col("email", "VARCHAR", vec![]);
    let masking = suggest_masking(&col, SqlDialect::Redshift).expect("should suggest");
    assert!(masking.contains("SHA2"));
}

#[test]
fn pii_masking_generic_email() {
    let col = make_col("email", "VARCHAR", vec![]);
    let masking = suggest_masking(&col, SqlDialect::Generic).expect("should suggest");
    assert!(masking.contains("MASKED"));
}

#[test]
fn pii_masking_snowflake_ssn() {
    let col = make_col("ssn", "VARCHAR", vec![]);
    let masking = suggest_masking(&col, SqlDialect::Snowflake).expect("should suggest");
    assert!(masking.contains("***-**-"));
}

#[test]
fn pii_masking_bigquery_dob() {
    let col = make_col("dob", "DATE", vec![]);
    let masking = suggest_masking(&col, SqlDialect::BigQuery).expect("should suggest");
    assert!(masking.contains("DATE_TRUNC"));
}

#[test]
fn pii_masking_non_pii_returns_none() {
    let col = make_col("id", "INT", vec![]);
    assert!(suggest_masking(&col, SqlDialect::Snowflake).is_none());
}

#[test]
fn pii_masking_snowflake_credit_card() {
    let col = make_col("credit_card", "VARCHAR", vec![]);
    let masking = suggest_masking(&col, SqlDialect::Snowflake).expect("should suggest");
    assert!(masking.contains("RIGHT"));
}

#[test]
fn pii_masking_postgres_first_name() {
    let col = make_col("first_name", "VARCHAR", vec![]);
    let masking = suggest_masking(&col, SqlDialect::Postgres).expect("should suggest");
    assert!(masking.contains("***"));
}

// ═══════════════════════════════════════════════════════════════════════
// Risk level tests
// ═══════════════════════════════════════════════════════════════════════

#[test]
fn pii_risk_level_ssn_is_high() {
    assert_eq!(PiiClassification::SSN.risk_level(), PiiRiskLevel::High);
}

#[test]
fn pii_risk_level_financial_is_high() {
    assert_eq!(
        PiiClassification::Financial.risk_level(),
        PiiRiskLevel::High
    );
}

#[test]
fn pii_risk_level_email_is_medium() {
    assert_eq!(PiiClassification::Email.risk_level(), PiiRiskLevel::Medium);
}

#[test]
fn pii_risk_level_name_is_low() {
    assert_eq!(PiiClassification::Name.risk_level(), PiiRiskLevel::Low);
}

// ═══════════════════════════════════════════════════════════════════════
// Serialization tests
// ═══════════════════════════════════════════════════════════════════════

#[test]
fn pii_report_serializes_to_json() {
    let schema = pii_heavy_schema();
    let report = classify_schema(&schema);
    let json = serde_json::to_string(&report).expect("should serialize");
    assert!(json.contains("pii_count"));
    assert!(json.contains("risk_level"));
}

#[test]
fn pii_query_result_serializes_to_json() {
    let schema = pii_heavy_schema();
    let result = check_query_pii("SELECT email FROM customers", &schema);
    let json = serde_json::to_string(&result).expect("should serialize");
    assert!(json.contains("accesses_pii"));
}
