//! End-to-End Tests: Cross-Database Federation Chains
//!
//! This test suite validates federation behavior across multiple database backends
//! and cross-database entity resolution chains.
//!
//! Test scenarios:
//! 1. PostgreSQL → PostgreSQL entity chain
//! 2. PostgreSQL → MySQL entity chain
//! 3. PostgreSQL → SQLite entity chain
//! 4. MySQL → PostgreSQL → SQLite chain (3 databases)
//! 5. Entity resolution with type conversion across databases
//! 6. Key field mapping across database types
//! 7. Field selection from different database backends
//! 8. Error handling for cross-database mismatches

#![allow(clippy::unwrap_used)] // Reason: test code, panics are acceptable
#![allow(clippy::needless_collect)] // Reason: intermediate collect is needed before assert_eq! consumes the iterator
#![allow(clippy::used_underscore_binding)] // Reason: test helper results prefixed with _ to suppress unused warnings
use std::collections::HashMap;

use fraiseql_core::federation::types::{
    EntityRepresentation, FederatedType, FederationMetadata, KeyDirective,
};
use serde_json::json;

// ============================================================================
// Test: Basic Single-Database Federation
// ============================================================================

#[test]
fn test_single_database_federation_postgresql() {
    // TEST: Single PostgreSQL instance serving federation
    // SCHEMA:
    //   Users table in PostgreSQL (source of truth)
    //   Orders table in PostgreSQL (references Users)
    //
    // WHEN: Entity resolution from PostgreSQL
    // THEN: Should resolve entities correctly

    let mut user_type = FederatedType::new("User".to_string());
    user_type.keys.push(KeyDirective {
        fields:     vec!["id".to_string()],
        resolvable: true,
    });

    let mut order_type = FederatedType::new("Order".to_string());
    order_type.keys.push(KeyDirective {
        fields:     vec!["id".to_string()],
        resolvable: true,
    });

    let metadata = FederationMetadata {
        enabled: true,
        version: "v2".to_string(),
        types: vec![user_type, order_type],
        remote_subscription_fields: HashMap::new(),
    };

    assert_eq!(metadata.types.len(), 2);
    assert_eq!(metadata.types[0].keys.len(), 1);
}

// ============================================================================
// Test: Cross-Database Entity Chains
// ============================================================================

#[test]
fn test_postgres_to_mysql_entity_chain() {
    // TEST: Entity resolution chain: PostgreSQL → MySQL
    // ARCHITECTURE:
    //   Users subgraph (PostgreSQL) - owns User entity
    //   Orders subgraph (MySQL)     - references User from PostgreSQL
    //
    // FLOW: Router requests User.orders → Orders MySQL → references User from PostgreSQL
    // WHEN: Entity is resolved across databases
    // THEN: Should handle type conversions transparently

    let mut user_type = FederatedType::new("User".to_string());
    user_type.keys.push(KeyDirective {
        fields:     vec!["id".to_string()],
        resolvable: true,
    });

    let mut order_type = FederatedType::new("Order".to_string());
    order_type.keys.push(KeyDirective {
        fields:     vec!["id".to_string()],
        resolvable: true,
    });

    let metadata = FederationMetadata {
        enabled: true,
        version: "v2".to_string(),
        types: vec![user_type, order_type],
        remote_subscription_fields: HashMap::new(),
    };

    // Simulate: User resolved from PostgreSQL
    let mut pg_user_fields = HashMap::new();
    pg_user_fields.insert("id".to_string(), json!("user-pg-123"));
    pg_user_fields.insert("name".to_string(), json!("Alice"));

    let pg_user = EntityRepresentation {
        typename:   "User".to_string(),
        key_fields: {
            let mut m = HashMap::new();
            m.insert("id".to_string(), json!("user-pg-123"));
            m
        },
        all_fields: pg_user_fields,
    };

    // Simulate: Order resolved from MySQL references the User
    let mut mysql_order_fields = HashMap::new();
    mysql_order_fields.insert("id".to_string(), json!("order-mysql-456"));
    mysql_order_fields.insert("user_id".to_string(), json!("user-pg-123"));
    mysql_order_fields.insert("total".to_string(), json!(99.99));

    let mysql_order = EntityRepresentation {
        typename:   "Order".to_string(),
        key_fields: {
            let mut m = HashMap::new();
            m.insert("id".to_string(), json!("order-mysql-456"));
            m
        },
        all_fields: mysql_order_fields,
    };

    // Both should be resolvable
    assert_eq!(pg_user.typename, "User");
    assert_eq!(mysql_order.typename, "Order");
    assert!(!metadata.types.is_empty());
}

#[test]
fn test_postgres_sqlite_entity_chain() {
    // TEST: Entity resolution: PostgreSQL → SQLite
    // SCENARIO:
    //   Users in PostgreSQL (primary production database)
    //   Local cache in SQLite (for offline support)
    //
    // WHEN: Entity requested, could come from either source
    // THEN: Should resolve correctly regardless of source

    let mut user_type = FederatedType::new("User".to_string());
    user_type.keys.push(KeyDirective {
        fields:     vec!["id".to_string()],
        resolvable: true,
    });

    let metadata = FederationMetadata {
        enabled: true,
        version: "v2".to_string(),
        types: vec![user_type],
        remote_subscription_fields: HashMap::new(),
    };

    // PostgreSQL source
    let mut pg_user_fields = HashMap::new();
    pg_user_fields.insert("id".to_string(), json!("user-123"));
    pg_user_fields.insert("email".to_string(), json!("alice@example.com"));

    let pg_user = EntityRepresentation {
        typename:   "User".to_string(),
        key_fields: {
            let mut m = HashMap::new();
            m.insert("id".to_string(), json!("user-123"));
            m
        },
        all_fields: pg_user_fields,
    };

    // SQLite source (same user, different database)
    let mut sqlite_user_fields = HashMap::new();
    sqlite_user_fields.insert("id".to_string(), json!("user-123"));
    sqlite_user_fields.insert("email".to_string(), json!("alice@example.com"));

    let sqlite_user = EntityRepresentation {
        typename:   "User".to_string(),
        key_fields: {
            let mut m = HashMap::new();
            m.insert("id".to_string(), json!("user-123"));
            m
        },
        all_fields: sqlite_user_fields,
    };

    // Both sources should produce same logical entity
    assert_eq!(pg_user.key_fields, sqlite_user.key_fields);
    assert_eq!(metadata.types[0].name, "User");
}

// ============================================================================
// Test: Multi-Database Chains (3+ Databases)
// ============================================================================

#[test]
fn test_three_database_chain_mysql_postgres_sqlite() {
    // TEST: Complex chain: MySQL → PostgreSQL → SQLite
    // ARCHITECTURE:
    //   Subgraph 1: Users in MySQL
    //   Subgraph 2: Orders in PostgreSQL (references Users)
    //   Subgraph 3: Inventory in SQLite (references Orders)
    //
    // FLOW: Router requests Order.inventory → PostgreSQL → SQLite
    //                                              ↓
    //                                    references User in MySQL
    //
    // WHEN: Full chain is executed
    // THEN: All 3 databases should coordinate correctly

    let mut user_type = FederatedType::new("User".to_string());
    user_type.keys.push(KeyDirective {
        fields:     vec!["id".to_string()],
        resolvable: true,
    });

    let mut order_type = FederatedType::new("Order".to_string());
    order_type.keys.push(KeyDirective {
        fields:     vec!["id".to_string()],
        resolvable: true,
    });

    let mut inventory_type = FederatedType::new("Inventory".to_string());
    inventory_type.keys.push(KeyDirective {
        fields:     vec!["order_id".to_string()],
        resolvable: true,
    });

    let metadata = FederationMetadata {
        enabled: true,
        version: "v2".to_string(),
        types: vec![user_type, order_type, inventory_type],
        remote_subscription_fields: HashMap::new(),
    };

    // MySQL User
    let mut mysql_user_fields = HashMap::new();
    mysql_user_fields.insert("id".to_string(), json!("user-mysql-1"));

    let _mysql_user = EntityRepresentation {
        typename:   "User".to_string(),
        key_fields: {
            let mut m = HashMap::new();
            m.insert("id".to_string(), json!("user-mysql-1"));
            m
        },
        all_fields: mysql_user_fields,
    };

    // PostgreSQL Order (references MySQL User)
    let mut pg_order_fields = HashMap::new();
    pg_order_fields.insert("id".to_string(), json!("order-pg-100"));
    pg_order_fields.insert("user_id".to_string(), json!("user-mysql-1"));

    let pg_order = EntityRepresentation {
        typename:   "Order".to_string(),
        key_fields: {
            let mut m = HashMap::new();
            m.insert("id".to_string(), json!("order-pg-100"));
            m
        },
        all_fields: pg_order_fields,
    };

    // SQLite Inventory (references PostgreSQL Order)
    let mut sqlite_inventory_fields = HashMap::new();
    sqlite_inventory_fields.insert("order_id".to_string(), json!("order-pg-100"));
    sqlite_inventory_fields.insert("status".to_string(), json!("in_stock"));

    let sqlite_inventory = EntityRepresentation {
        typename:   "Inventory".to_string(),
        key_fields: {
            let mut m = HashMap::new();
            m.insert("order_id".to_string(), json!("order-pg-100"));
            m
        },
        all_fields: sqlite_inventory_fields,
    };

    // All entities should be resolvable
    assert!(pg_order.key_fields.contains_key("id"));
    assert!(sqlite_inventory.key_fields.contains_key("order_id"));
    assert_eq!(metadata.types.len(), 3);
}

// ============================================================================
// Test: Key Field Mapping Across Databases
// ============================================================================

#[test]
fn test_key_field_type_conversion_string_int() {
    // TEST: Key field type conversion: String (PostgreSQL) ↔ INT (MySQL)
    // SCENARIO:
    //   PostgreSQL stores user_id as TEXT
    //   MySQL stores user_id as INT
    //
    // WHEN: Entity resolution across these databases
    // THEN: Should handle type conversion (string to int and back)

    let mut user_type = FederatedType::new("User".to_string());
    user_type.keys.push(KeyDirective {
        fields:     vec!["id".to_string()],
        resolvable: true,
    });

    let metadata = FederationMetadata {
        enabled: true,
        version: "v2".to_string(),
        types: vec![user_type],
        remote_subscription_fields: HashMap::new(),
    };

    // PostgreSQL: TEXT id
    let mut pg_user_fields = HashMap::new();
    pg_user_fields.insert("id".to_string(), json!("12345"));

    let pg_user = EntityRepresentation {
        typename:   "User".to_string(),
        key_fields: {
            let mut m = HashMap::new();
            m.insert("id".to_string(), json!("12345"));
            m
        },
        all_fields: pg_user_fields,
    };

    // MySQL: INT id (same logical value)
    let mut mysql_user_fields = HashMap::new();
    mysql_user_fields.insert("id".to_string(), json!(12345));

    let mysql_user = EntityRepresentation {
        typename:   "User".to_string(),
        key_fields: {
            let mut m = HashMap::new();
            m.insert("id".to_string(), json!(12345));
            m
        },
        all_fields: mysql_user_fields,
    };

    // Both should be valid entities (even with different JSON types)
    assert_eq!(pg_user.typename, mysql_user.typename);
    assert_eq!(metadata.types[0].name, "User");
}

// ============================================================================
// Test: Field Selection from Different Databases
// ============================================================================

#[test]
fn test_field_selection_across_databases() {
    // TEST: Select different fields from different databases
    // SCENARIO:
    //   User.id comes from PostgreSQL
    //   User.profile comes from MySQL
    //
    // WHEN: Resolving user with fields from multiple sources
    // THEN: Should aggregate fields correctly

    let mut user_type = FederatedType::new("User".to_string());
    user_type.keys.push(KeyDirective {
        fields:     vec!["id".to_string()],
        resolvable: true,
    });

    let _metadata = FederationMetadata {
        enabled: true,
        version: "v2".to_string(),
        types: vec![user_type],
        remote_subscription_fields: HashMap::new(),
    };

    // PostgreSQL provides: id, created_at
    let mut pg_user_fields = HashMap::new();
    pg_user_fields.insert("id".to_string(), json!("user-123"));
    pg_user_fields.insert("created_at".to_string(), json!("2024-01-01"));

    let pg_partial_user = EntityRepresentation {
        typename:   "User".to_string(),
        key_fields: {
            let mut m = HashMap::new();
            m.insert("id".to_string(), json!("user-123"));
            m
        },
        all_fields: pg_user_fields,
    };

    // MySQL provides: id, profile
    let mut mysql_user_fields = HashMap::new();
    mysql_user_fields.insert("id".to_string(), json!("user-123"));
    mysql_user_fields.insert("profile".to_string(), json!({"bio": "Engineer"}));

    let mysql_partial_user = EntityRepresentation {
        typename:   "User".to_string(),
        key_fields: {
            let mut m = HashMap::new();
            m.insert("id".to_string(), json!("user-123"));
            m
        },
        all_fields: mysql_user_fields,
    };

    // Both have different fields but same key
    assert_eq!(pg_partial_user.key_fields, mysql_partial_user.key_fields);
    assert!(pg_partial_user.has_field("created_at"));
    assert!(mysql_partial_user.has_field("profile"));
}

// ============================================================================
// Test: Consistency Across Database Boundaries
// ============================================================================

#[test]
fn test_entity_consistency_across_databases() {
    // TEST: Entity should be consistent when resolved from different databases
    // SCENARIO:
    //   User exists in both PostgreSQL and MySQL (replicated)
    //
    // WHEN: User resolved from either database
    // THEN: Key fields should be identical

    let _user_type = FederatedType::new("User".to_string());

    // PostgreSQL version
    let mut pg_fields = HashMap::new();
    pg_fields.insert("id".to_string(), json!("user-123"));
    pg_fields.insert("email".to_string(), json!("alice@example.com"));
    pg_fields.insert("name".to_string(), json!("Alice Smith"));

    let pg_user = EntityRepresentation {
        typename:   "User".to_string(),
        key_fields: {
            let mut m = HashMap::new();
            m.insert("id".to_string(), json!("user-123"));
            m
        },
        all_fields: pg_fields,
    };

    // MySQL version (same data)
    let mut mysql_fields = HashMap::new();
    mysql_fields.insert("id".to_string(), json!("user-123"));
    mysql_fields.insert("email".to_string(), json!("alice@example.com"));
    mysql_fields.insert("name".to_string(), json!("Alice Smith"));

    let mysql_user = EntityRepresentation {
        typename:   "User".to_string(),
        key_fields: {
            let mut m = HashMap::new();
            m.insert("id".to_string(), json!("user-123"));
            m
        },
        all_fields: mysql_fields,
    };

    // Key fields must be identical
    assert_eq!(pg_user.key_fields, mysql_user.key_fields);
    assert_eq!(pg_user.typename, mysql_user.typename);
}

// ============================================================================
// Test: Error Handling for Database Mismatches
// ============================================================================

#[test]
fn test_missing_entity_in_secondary_database() {
    // TEST: Handle case where entity exists in one DB but not other
    // SCENARIO:
    //   User exists in PostgreSQL
    //   User doesn't exist in MySQL
    //
    // WHEN: Requested from MySQL
    // THEN: Should return None/error appropriately

    let user_type = FederatedType::new("User".to_string());

    let _metadata = FederationMetadata {
        enabled: true,
        version: "v2".to_string(),
        types: vec![user_type],
        remote_subscription_fields: HashMap::new(),
    };

    // PostgreSQL has this user
    let mut pg_fields = HashMap::new();
    pg_fields.insert("id".to_string(), json!("user-999"));

    let _pg_user = EntityRepresentation {
        typename:   "User".to_string(),
        key_fields: {
            let mut m = HashMap::new();
            m.insert("id".to_string(), json!("user-999"));
            m
        },
        all_fields: pg_fields,
    };

    // MySQL doesn't have this user (would be None/empty)
    let mysql_user: Option<EntityRepresentation> = None;

    // Verify that not all sources might have the entity
    assert!(mysql_user.is_none());
}

#[test]
fn test_database_type_mismatch_in_keys() {
    // TEST: Handle key type mismatch across databases
    // SCENARIO:
    //   Entity key in PostgreSQL: STRING
    //   Entity key in MySQL: INTEGER
    //   Same logical entity but different types
    //
    // WHEN: Trying to match keys
    // THEN: Should handle type coercion or return error

    let _user_type = FederatedType::new("User".to_string());

    // PostgreSQL: key as string
    let pg_key_fields = {
        let mut m = HashMap::new();
        m.insert("id".to_string(), json!("12345"));
        m
    };

    // MySQL: key as int
    let mysql_key_fields = {
        let mut m = HashMap::new();
        m.insert("id".to_string(), json!(12345));
        m
    };

    // Keys are logically equivalent but have different JSON types
    assert_ne!(pg_key_fields, mysql_key_fields);
}

// ============================================================================
// Test: Performance Considerations
// ============================================================================

#[test]
fn test_batch_entity_resolution_multiple_databases() {
    // TEST: Batch resolution across multiple databases
    // SCENARIO:
    //   Resolve 100 orders, each might come from different database
    //
    // WHEN: Batching entities across databases
    // THEN: Should handle efficiently

    let mut order_type = FederatedType::new("Order".to_string());
    order_type.keys.push(KeyDirective {
        fields:     vec!["id".to_string()],
        resolvable: true,
    });

    let _metadata = FederationMetadata {
        enabled: true,
        version: "v2".to_string(),
        types: vec![order_type],
        remote_subscription_fields: HashMap::new(),
    };

    // Simulate 50 orders from PostgreSQL
    let _pg_orders: Vec<EntityRepresentation> = (0..50)
        .map(|i| EntityRepresentation {
            typename:   "Order".to_string(),
            key_fields: {
                let mut m = HashMap::new();
                m.insert("id".to_string(), json!(format!("pg-order-{}", i)));
                m
            },
            all_fields: {
                let mut m = HashMap::new();
                m.insert("id".to_string(), json!(format!("pg-order-{}", i)));
                m.insert("total".to_string(), json!(99.99 + f64::from(i)));
                m
            },
        })
        .collect();

    // Simulate 50 orders from MySQL
    let _mysql_orders: Vec<EntityRepresentation> = (50..100)
        .map(|i| EntityRepresentation {
            typename:   "Order".to_string(),
            key_fields: {
                let mut m = HashMap::new();
                m.insert("id".to_string(), json!(format!("mysql-order-{}", i)));
                m
            },
            all_fields: {
                let mut m = HashMap::new();
                m.insert("id".to_string(), json!(format!("mysql-order-{}", i)));
                m.insert("total".to_string(), json!(49.99 + f64::from(i - 50)));
                m
            },
        })
        .collect();

    // Verify batch can be processed
    assert_eq!(_pg_orders.len(), 50);
    assert_eq!(_mysql_orders.len(), 50);
}
