//! Integration tests for BLE transport
//!
//! Phase 3.1: BLE GATT Implementation
//!
//! These tests verify the BLE transport provider functionality including:
//! - Central mode scanning
//! - Connection establishment
//! - Send/receive roundtrip (via mocks when no hardware available)
//! - Connection pool limits
//! - Session resumption
//!
//! # Hardware Requirements
//!
//! Some tests require BLE hardware and are marked with `#[ignore]`.
//! Default builds include BLE support; stripped builds can run them with:
//! `cargo test --features ble -- --ignored`
//!
//! # Platform Support
//!
//! - **Linux**: BlueZ via btleplug
//! - **macOS**: Core Bluetooth via btleplug
//! - **Windows**: WinRT via btleplug

#![cfg(feature = "ble")]
#![allow(clippy::unwrap_used, clippy::expect_used)]

use ant_quic::transport::{
    ANT_QUIC_SERVICE_UUID, BleConfig, BleConnection, BleConnectionState, BleTransport,
    CCCD_DISABLE, CCCD_ENABLE_INDICATION, CCCD_ENABLE_NOTIFICATION, CCCD_UUID,
    CharacteristicHandle, ConnectionPoolStats, DiscoveredDevice, RX_CHARACTERISTIC_UUID,
    ResumeToken, ScanState, TX_CHARACTERISTIC_UUID, TransportCapabilities, TransportProvider,
    TransportType,
};
use std::time::Duration;

// ============================================================================
// GATT Constants Tests
// ============================================================================

#[test]
fn test_service_uuid_format() {
    // Verify the service UUID is in correct format
    assert_eq!(ANT_QUIC_SERVICE_UUID.len(), 16, "UUID should be 16 bytes");

    // Verify it starts with our expected prefix
    assert_eq!(
        &ANT_QUIC_SERVICE_UUID[..4],
        &[0xa0, 0x3d, 0x7e, 0x9f],
        "Service UUID should have correct prefix"
    );

    // Verify it ends with 0x01 (service marker)
    assert_eq!(
        ANT_QUIC_SERVICE_UUID[15], 0x01,
        "Service UUID should end with 0x01"
    );
}

#[test]
fn test_characteristic_uuids_are_distinct() {
    // TX and RX must have different UUIDs
    assert_ne!(
        TX_CHARACTERISTIC_UUID, RX_CHARACTERISTIC_UUID,
        "TX and RX UUIDs must be different"
    );

    // Both should share the same prefix as the service
    assert_eq!(
        &TX_CHARACTERISTIC_UUID[..4],
        &RX_CHARACTERISTIC_UUID[..4],
        "Characteristics should share service prefix"
    );

    // TX ends with 0x02, RX ends with 0x03
    assert_eq!(
        TX_CHARACTERISTIC_UUID[15], 0x02,
        "TX UUID should end with 0x02"
    );
    assert_eq!(
        RX_CHARACTERISTIC_UUID[15], 0x03,
        "RX UUID should end with 0x03"
    );
}

#[test]
fn test_cccd_uuid_is_bluetooth_sig_standard() {
    // CCCD UUID should be the Bluetooth SIG standard 0x2902
    // In 128-bit form: 00002902-0000-1000-8000-00805f9b34fb
    assert_eq!(CCCD_UUID.len(), 16, "CCCD UUID should be 16 bytes");

    // The short form 0x2902 appears at bytes 2-3
    assert_eq!(CCCD_UUID[2], 0x29, "CCCD should have 0x29 at position 2");
    assert_eq!(CCCD_UUID[3], 0x02, "CCCD should have 0x02 at position 3");
}

#[test]
fn test_cccd_values() {
    // Verify CCCD enable/disable values per Bluetooth spec
    assert_eq!(
        CCCD_ENABLE_NOTIFICATION,
        [0x01, 0x00],
        "Enable notification = 0x0001"
    );
    assert_eq!(
        CCCD_ENABLE_INDICATION,
        [0x02, 0x00],
        "Enable indication = 0x0002"
    );
    assert_eq!(CCCD_DISABLE, [0x00, 0x00], "Disable = 0x0000");
}

// ============================================================================
// BleConnection State Machine Tests
// ============================================================================

#[tokio::test]
async fn test_ble_connection_initial_state() {
    let device_id = [0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
    let conn = BleConnection::new(device_id);

    assert_eq!(conn.device_id(), device_id);
    assert_eq!(conn.state().await, BleConnectionState::Discovered);
    assert!(!conn.is_connected().await);
    assert!(conn.connection_duration().is_none());
}

#[tokio::test]
async fn test_ble_connection_state_transitions() {
    let device_id = [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF];
    let mut conn = BleConnection::new(device_id);

    // Discovered -> Connecting
    assert!(conn.start_connecting().await.is_ok());
    assert_eq!(conn.state().await, BleConnectionState::Connecting);

    // Connecting -> Connected
    let tx_char = CharacteristicHandle::tx();
    let rx_char = CharacteristicHandle::rx();
    conn.mark_connected(tx_char.clone(), rx_char.clone()).await;
    assert_eq!(conn.state().await, BleConnectionState::Connected);
    assert!(conn.is_connected().await);
    assert!(conn.connection_duration().is_some());

    // Connected -> Disconnecting
    assert!(conn.start_disconnect().await.is_ok());
    assert_eq!(conn.state().await, BleConnectionState::Disconnecting);

    // Disconnecting -> Disconnected
    conn.mark_disconnected().await;
    assert_eq!(conn.state().await, BleConnectionState::Disconnected);
    assert!(!conn.is_connected().await);
}

#[tokio::test]
async fn test_ble_connection_invalid_transitions() {
    let device_id = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55];
    let conn = BleConnection::new(device_id);

    // Discovered -> cannot disconnect directly
    let result = conn.start_disconnect().await;
    assert!(result.is_err(), "Cannot disconnect from Discovered state");
}

#[tokio::test]
async fn test_ble_connection_activity_tracking() {
    let device_id = [0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE];
    let conn = BleConnection::new(device_id);

    let idle_before = conn.idle_duration().await;

    // Sleep briefly
    tokio::time::sleep(Duration::from_millis(50)).await;

    let idle_after = conn.idle_duration().await;
    assert!(idle_after > idle_before, "Idle duration should increase");

    // Touch to reset activity
    conn.touch().await;

    let idle_after_touch = conn.idle_duration().await;
    assert!(
        idle_after_touch < idle_after,
        "Idle duration should reset after touch"
    );
}

#[test]
fn test_ble_connection_state_display() {
    assert_eq!(format!("{}", BleConnectionState::Discovered), "discovered");
    assert_eq!(format!("{}", BleConnectionState::Connecting), "connecting");
    assert_eq!(format!("{}", BleConnectionState::Connected), "connected");
    assert_eq!(
        format!("{}", BleConnectionState::Disconnecting),
        "disconnecting"
    );
    assert_eq!(
        format!("{}", BleConnectionState::Disconnected),
        "disconnected"
    );
}

// ============================================================================
// CharacteristicHandle Tests
// ============================================================================

#[test]
fn test_characteristic_handle_tx() {
    let tx = CharacteristicHandle::tx();

    assert_eq!(tx.uuid, TX_CHARACTERISTIC_UUID);
    assert!(
        tx.write_without_response,
        "TX should support write without response"
    );
    assert!(!tx.notify, "TX should not support notify");
    assert!(!tx.indicate, "TX should not support indicate");
}

#[test]
fn test_characteristic_handle_rx() {
    let rx = CharacteristicHandle::rx();

    assert_eq!(rx.uuid, RX_CHARACTERISTIC_UUID);
    assert!(!rx.write_without_response, "RX should not support write");
    assert!(rx.notify, "RX should support notify");
    assert!(!rx.indicate, "RX should not support indicate");
}

// ============================================================================
// BleConfig Tests
// ============================================================================

#[test]
fn test_ble_config_default() {
    let config = BleConfig::default();

    assert_eq!(config.service_uuid, ANT_QUIC_SERVICE_UUID);
    assert_eq!(
        config.session_cache_duration,
        Duration::from_secs(24 * 60 * 60)
    );
    assert_eq!(config.max_connections, 5);
    assert_eq!(config.scan_interval, Duration::from_secs(10));
    assert_eq!(config.connection_timeout, Duration::from_secs(30));
}

// ============================================================================
// ResumeToken Tests
// ============================================================================

#[test]
fn test_resume_token_serialization() {
    let token = ResumeToken {
        peer_id_hash: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
        session_hash: [
            17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
        ],
    };

    let bytes = token.to_bytes();
    assert_eq!(bytes.len(), 32, "Token should serialize to 32 bytes");

    // First 16 bytes are peer_id_hash
    assert_eq!(&bytes[..16], &token.peer_id_hash);

    // Last 16 bytes are session_hash
    assert_eq!(&bytes[16..], &token.session_hash);

    // Round-trip
    let restored = ResumeToken::from_bytes(&bytes);
    assert_eq!(restored.peer_id_hash, token.peer_id_hash);
    assert_eq!(restored.session_hash, token.session_hash);
}

// ============================================================================
// DiscoveredDevice Tests
// ============================================================================

#[test]
fn test_discovered_device_creation() {
    let device_id = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06];
    let device = DiscoveredDevice::new(device_id);

    assert_eq!(device.device_id, device_id);
    assert!(device.local_name.is_none());
    assert!(device.rssi.is_none());
    assert!(!device.has_service);
    assert!(device.age() < Duration::from_secs(1));
}

#[test]
fn test_discovered_device_is_recent() {
    let device = DiscoveredDevice::new([0; 6]);

    // Should be recent immediately after creation
    assert!(device.is_recent(Duration::from_secs(1)));

    // Should not be recent with very short threshold
    assert!(!device.is_recent(Duration::ZERO));
}

#[test]
fn test_discovered_device_update() {
    let mut device = DiscoveredDevice::new([0xAA; 6]);
    let first_seen = device.last_seen;

    std::thread::sleep(Duration::from_millis(10));

    device.update_last_seen();
    assert!(device.last_seen > first_seen);
}

// ============================================================================
// ScanState Tests
// ============================================================================

#[test]
fn test_scan_state_display() {
    assert_eq!(format!("{}", ScanState::Idle), "idle");
    assert_eq!(format!("{}", ScanState::Scanning), "scanning");
    assert_eq!(format!("{}", ScanState::Stopping), "stopping");
}

#[test]
fn test_scan_state_default() {
    assert_eq!(ScanState::default(), ScanState::Idle);
}

// ============================================================================
// TransportCapabilities Tests
// ============================================================================

#[test]
fn test_ble_capabilities() {
    let caps = TransportCapabilities::ble();

    // BLE has limited MTU
    assert!(caps.mtu < 1200, "BLE MTU should be less than 1200 bytes");

    // BLE should use constrained engine
    assert!(
        !caps.supports_full_quic(),
        "BLE should not support full QUIC"
    );

    // BLE is power constrained
    assert!(caps.power_constrained, "BLE should be power constrained");

    // BLE has link-layer acknowledgments
    assert!(caps.link_layer_acks, "BLE should have link-layer acks");
}

// ============================================================================
// ConnectionPoolStats Tests
// ============================================================================

#[test]
fn test_connection_pool_stats_default() {
    let stats = ConnectionPoolStats::default();

    assert_eq!(stats.active, 0);
    assert_eq!(stats.max_connections, 0);
    assert_eq!(stats.connecting, 0);
    assert_eq!(stats.disconnecting, 0);
    assert_eq!(stats.total, 0);
    assert!(stats.oldest_idle.is_none());
}

#[test]
fn test_connection_pool_stats_capacity() {
    let stats = ConnectionPoolStats {
        active: 3,
        max_connections: 5,
        connecting: 0,
        disconnecting: 0,
        total: 3,
        oldest_idle: Some(Duration::from_secs(60)),
    };

    assert!(stats.has_capacity(), "3 < 5 should have capacity");
}

#[test]
fn test_connection_pool_stats_no_capacity() {
    let stats = ConnectionPoolStats {
        active: 5,
        max_connections: 5,
        connecting: 0,
        disconnecting: 0,
        total: 5,
        oldest_idle: Some(Duration::from_secs(120)),
    };

    assert!(!stats.has_capacity(), "5 >= 5 should not have capacity");
}

// ============================================================================
// BleTransport Integration Tests (Require Hardware)
// ============================================================================

/// Test that BleTransport can be created with default config
///
/// This test requires BLE hardware.
#[tokio::test]
#[ignore = "requires BLE hardware"]
async fn test_ble_transport_creation() {
    let result = BleTransport::new().await;

    match result {
        Ok(transport) => {
            assert_eq!(transport.transport_type(), TransportType::Ble);
            assert!(transport.is_online());
            assert!(transport.local_addr().is_some());
        }
        Err(e) => {
            // On systems without BLE, this is expected
            println!("BLE transport creation failed (no hardware?): {e}");
        }
    }
}

/// Test that BleTransport can be created with custom config
#[tokio::test]
#[ignore = "requires BLE hardware"]
async fn test_ble_transport_with_config() {
    let config = BleConfig {
        max_connections: 3,
        session_cache_duration: Duration::from_secs(3600),
        ..Default::default()
    };

    match BleTransport::with_config(config).await {
        Ok(transport) => {
            assert_eq!(transport.transport_type(), TransportType::Ble);
        }
        Err(e) => {
            println!("BLE transport creation failed: {e}");
        }
    }
}

/// Test scanning for BLE devices
#[tokio::test]
#[ignore = "requires BLE hardware"]
async fn test_ble_transport_scanning() {
    let transport = match BleTransport::new().await {
        Ok(t) => t,
        Err(e) => {
            println!("Skipping test (no BLE hardware): {e}");
            return;
        }
    };

    // Start scanning
    let result = transport.start_scanning().await;
    assert!(result.is_ok(), "Scanning should start");

    let scan_state = transport.scan_state().await;
    assert_eq!(scan_state, ScanState::Scanning);

    // Scan for a bit
    tokio::time::sleep(Duration::from_secs(5)).await;

    // Stop scanning
    let result = transport.stop_scanning().await;
    assert!(result.is_ok(), "Scanning should stop");

    // Check discovered devices
    let devices = transport.discovered_devices().await;
    println!("Discovered {} BLE devices", devices.len());

    for device in &devices {
        println!(
            "  Device {:02x?}: name={:?}, rssi={:?}, has_service={}",
            device.device_id, device.local_name, device.rssi, device.has_service
        );
    }
}

/// Test connection to a BLE device
#[tokio::test]
#[ignore = "requires BLE hardware and nearby ant-quic peer"]
async fn test_ble_transport_connection() {
    let transport = match BleTransport::new().await {
        Ok(t) => t,
        Err(e) => {
            println!("Skipping test (no BLE hardware): {e}");
            return;
        }
    };

    // Start scanning
    transport.start_scanning().await.expect("scan start failed");

    // Wait for device discovery
    tokio::time::sleep(Duration::from_secs(10)).await;

    // Get discovered devices with our service
    let devices = transport.discovered_devices().await;
    let ant_quic_devices: Vec<_> = devices.iter().filter(|d| d.has_service).collect();

    if ant_quic_devices.is_empty() {
        println!("No ant-quic BLE peers found");
        return;
    }

    // Try to connect to the first one
    let target = ant_quic_devices[0];
    println!("Connecting to device {:02x?}", target.device_id);

    let result = transport.connect_to_device(target.device_id).await;
    assert!(result.is_ok(), "Connection should succeed");

    // Verify we're connected
    let stats = transport.pool_stats().await;
    assert_eq!(stats.active, 1);

    // Disconnect
    transport
        .disconnect_from_device(&target.device_id)
        .await
        .ok();
}

/// Test send/receive data over BLE
#[tokio::test]
#[ignore = "requires BLE hardware and nearby ant-quic peer"]
async fn test_ble_transport_data_transfer() {
    let _transport = match BleTransport::new().await {
        Ok(t) => t,
        Err(e) => {
            println!("Skipping test (no BLE hardware): {e}");
            return;
        }
    };

    // This test requires a connected peer
    // Implementation would:
    // 1. Connect to a peer
    // 2. Send test data via TX characteristic
    // 3. Receive response via RX notifications
    // 4. Verify data integrity

    println!("BLE data transfer test placeholder - requires real peer");
}

// ============================================================================
// Mock-based Tests (No Hardware Required)
// ============================================================================

/// Test connection pool eviction
#[tokio::test]
async fn test_connection_pool_eviction_logic() {
    // Test the LRU eviction logic without real BLE hardware
    let stats = ConnectionPoolStats {
        active: 5,
        max_connections: 5,
        connecting: 0,
        disconnecting: 0,
        total: 5,
        oldest_idle: Some(Duration::from_secs(3600)),
    };

    // Pool is at capacity
    assert!(!stats.has_capacity());

    // After eviction, should have capacity
    let after_eviction = ConnectionPoolStats {
        active: 4,
        max_connections: 5,
        connecting: 0,
        disconnecting: 0,
        total: 4,
        oldest_idle: Some(Duration::from_secs(3500)),
    };

    assert!(after_eviction.has_capacity());
}

/// Test session resumption token size is efficient
#[test]
fn test_resume_token_efficiency() {
    // Session token should be much smaller than full PQC handshake
    let token_size = std::mem::size_of::<ResumeToken>();

    // Full ML-KEM-768 ciphertext is 1088 bytes
    // Full ML-DSA-65 signature is 3309 bytes
    // Resume token should be under 100 bytes
    assert!(
        token_size < 100,
        "Resume token should be efficient (< 100 bytes), got {token_size}"
    );

    // Serialized token is exactly 32 bytes
    let token = ResumeToken {
        peer_id_hash: [0; 16],
        session_hash: [0; 16],
    };
    assert_eq!(token.to_bytes().len(), 32);
}

/// Test that BLE capabilities indicate constrained engine
#[test]
fn test_ble_uses_constrained_engine() {
    use ant_quic::transport::ProtocolEngine;

    let caps = TransportCapabilities::ble();
    let engine = ProtocolEngine::for_transport(&caps);

    assert_eq!(
        engine,
        ProtocolEngine::Constrained,
        "BLE should use constrained engine due to MTU limitations"
    );
}

/// Test BLE address format
#[test]
fn test_ble_address_format() {
    use ant_quic::transport::TransportAddr;

    // Create a BLE address
    let device_id = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55];
    let addr = TransportAddr::ble(device_id, None);

    assert_eq!(addr.transport_type(), TransportType::Ble);

    // With service UUID
    let service_uuid = [
        0xa0, 0x3d, 0x7e, 0x9f, 0x0b, 0xca, 0x12, 0xfe, 0xa6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x01,
    ];
    let addr_with_service = TransportAddr::ble(device_id, Some(service_uuid));
    assert_eq!(addr_with_service.transport_type(), TransportType::Ble);
}

// ============================================================================
// Session Cache Tests
// ============================================================================

#[test]
fn test_session_cache_eviction_criteria() {
    // Sessions should expire after configured duration
    let config = BleConfig::default();
    let cache_duration = config.session_cache_duration;

    // Default is 24 hours
    assert_eq!(cache_duration, Duration::from_secs(24 * 60 * 60));

    // Session older than this should be evicted
    // (tested via BleTransport::lookup_session internally)
}

// ============================================================================
// Platform-Specific Tests
// ============================================================================

#[cfg(target_os = "linux")]
#[test]
fn test_linux_bluez_support() {
    // Verify we're compiling with btleplug's Linux backend
    // The #[cfg] attribute ensures this only compiles on Linux
    println!("Linux BlueZ backend enabled");
}

#[cfg(target_os = "macos")]
#[test]
fn test_macos_core_bluetooth_support() {
    // Verify we're compiling with btleplug's macOS backend
    // The #[cfg] attribute ensures this only compiles on macOS
    println!("macOS Core Bluetooth backend enabled");
}

#[cfg(target_os = "windows")]
#[test]
fn test_windows_winrt_support() {
    // Verify we're compiling with btleplug's Windows backend
    // The #[cfg] attribute ensures this only compiles on Windows
    println!("Windows WinRT backend enabled");
}

// ============================================================================
// Edge Cases and Error Handling
// ============================================================================

#[tokio::test]
async fn test_connection_to_invalid_device_id() {
    // All-zero device ID should be rejected or handled gracefully
    let device_id = [0x00; 6];
    let conn = BleConnection::new(device_id);

    // Should start in discovered state regardless
    assert_eq!(conn.state().await, BleConnectionState::Discovered);
}

#[test]
fn test_discovered_device_stale_detection() {
    let device = DiscoveredDevice::new([0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x00]);

    // Immediately after creation
    assert!(device.is_recent(Duration::from_secs(60)));

    // Simulate time passing (we can't actually wait in unit tests)
    // The age() method uses Instant::now() so we verify the API works
    let age = device.age();
    assert!(age < Duration::from_secs(1));
}

#[test]
fn test_connection_state_debug_formatting() {
    let device_id = [0xCA, 0xFE, 0xBA, 0xBE, 0x00, 0x01];
    let conn = BleConnection::new(device_id);

    // Verify debug output doesn't panic
    let debug_str = format!("{:?}", conn);
    assert!(debug_str.contains("BleConnection"));
}
