use super::super::super::*;

// ========================================================================
// TILING-SPEC-001: Tile Profiling Tests (F356-F365)
// ========================================================================

/// F356: TileLevel enum coverage
#[test]
fn test_f356_tile_level_names() {
    assert_eq!(TileLevel::Macro.name(), "macro");
    assert_eq!(TileLevel::Midi.name(), "midi");
    assert_eq!(TileLevel::Micro.name(), "micro");
}

/// F357: TileStats basic operations
#[test]
fn test_f357_tile_stats_basic() {
    let mut stats = TileStats::new(TileLevel::Macro);
    assert_eq!(stats.count, 0);
    assert_eq!(stats.level, TileLevel::Macro);

    // Add samples
    stats.add_sample(1_000_000, 1024, 2048);
    stats.add_sample(2_000_000, 2048, 4096);

    assert_eq!(stats.count, 2);
    assert_eq!(stats.total_ns, 3_000_000);
    assert_eq!(stats.total_elements, 3072);
    assert_eq!(stats.total_flops, 6144);
    assert_eq!(stats.min_ns, 1_000_000);
    assert_eq!(stats.max_ns, 2_000_000);
}

/// F358: TileStats avg_us calculation
#[test]
fn test_f358_tile_stats_avg_us() {
    let mut stats = TileStats::new(TileLevel::Midi);
    assert_eq!(stats.avg_us(), 0.0);

    stats.add_sample(1_000_000, 100, 200); // 1ms
    stats.add_sample(3_000_000, 100, 200); // 3ms

    // Average should be 2ms = 2000µs
    assert!((stats.avg_us() - 2000.0).abs() < 0.01);
}

/// F359: TileStats throughput calculation
#[test]
fn test_f359_tile_stats_throughput() {
    let mut stats = TileStats::new(TileLevel::Micro);

    // 1 second worth of samples, 1M elements
    stats.add_sample(1_000_000_000, 1_000_000, 0);

    // Throughput should be 1M elem/s
    let throughput = stats.throughput();
    assert!((throughput - 1_000_000.0).abs() < 10.0);
}

/// F360: TileStats GFLOP/s calculation
#[test]
fn test_f360_tile_stats_gflops() {
    let mut stats = TileStats::new(TileLevel::Macro);

    // 100ms, 1 GFLOP
    stats.add_sample(100_000_000, 1000, 1_000_000_000);

    // GFLOP/s should be 10
    let gflops = stats.gflops();
    assert!((gflops - 10.0).abs() < 0.1);
}

/// F361: TileStats arithmetic intensity
#[test]
fn test_f361_tile_stats_arithmetic_intensity() {
    let mut stats = TileStats::new(TileLevel::Midi);

    // 1000 elements (4000 bytes), 8000 FLOPs -> AI = 2.0
    stats.add_sample(1_000_000, 1000, 8000);

    let ai = stats.arithmetic_intensity();
    assert!((ai - 2.0).abs() < 0.01);
}

/// F362: TileStats cache efficiency
#[test]
fn test_f362_tile_stats_cache_efficiency() {
    let mut stats = TileStats::new(TileLevel::Micro);

    // 100ms, 10 GFLOP -> 100 GFLOP/s
    stats.add_sample(100_000_000, 1000, 10_000_000_000);

    // Peak 200 GFLOP/s -> efficiency 0.5
    let efficiency = stats.cache_efficiency(200.0);
    assert!((efficiency - 0.5).abs() < 0.01);

    // Zero peak -> efficiency 0.0
    assert_eq!(stats.cache_efficiency(0.0), 0.0);
}

/// F363: BrickProfiler tile profiling enable/disable
#[test]
fn test_f363_brick_profiler_tile_enable() {
    let mut profiler = BrickProfiler::new();

    // Disabled by default
    assert!(!profiler.is_tile_profiling_enabled());

    // Enable
    profiler.enable_tile_profiling();
    assert!(profiler.is_tile_profiling_enabled());

    // Disable
    profiler.disable_tile_profiling();
    assert!(!profiler.is_tile_profiling_enabled());
}

/// F364: BrickProfiler start_tile/stop_tile
#[test]
fn test_f364_brick_profiler_tile_timing() {
    let mut profiler = BrickProfiler::new();
    profiler.enable_tile_profiling();

    // Time a macro tile
    let timer = profiler.start_tile(TileLevel::Macro, 0, 0);
    std::thread::sleep(std::time::Duration::from_micros(100));
    profiler.stop_tile(timer, 1024, 2048);

    // Time a midi tile
    let timer = profiler.start_tile(TileLevel::Midi, 1, 2);
    std::thread::sleep(std::time::Duration::from_micros(50));
    profiler.stop_tile(timer, 512, 1024);

    // Verify stats
    let macro_stats = profiler.tile_stats(TileLevel::Macro);
    assert_eq!(macro_stats.count, 1);
    assert!(macro_stats.total_ns > 0);
    assert_eq!(macro_stats.total_elements, 1024);

    let midi_stats = profiler.tile_stats(TileLevel::Midi);
    assert_eq!(midi_stats.count, 1);
    assert_eq!(midi_stats.total_elements, 512);
}

/// F365: BrickProfiler tile_summary report
#[test]
fn test_f365_brick_profiler_tile_summary() {
    let mut profiler = BrickProfiler::new();
    profiler.enable_tile_profiling();

    // Add some tile samples
    for i in 0..10 {
        let timer = profiler.start_tile(TileLevel::Macro, i, 0);
        profiler.stop_tile(timer, 65536, 2 * 65536);
    }

    for i in 0..100 {
        let timer = profiler.start_tile(TileLevel::Midi, i, 0);
        profiler.stop_tile(timer, 4096, 2 * 4096);
    }

    let summary = profiler.tile_summary();
    assert!(summary.contains("TILING-SPEC-001"));
    assert!(summary.contains("macro"));
    assert!(summary.contains("midi"));
}

/// F366: BrickProfiler tile reset
#[test]
fn test_f366_brick_profiler_tile_reset() {
    let mut profiler = BrickProfiler::new();
    profiler.enable_tile_profiling();

    // Add samples
    let timer = profiler.start_tile(TileLevel::Macro, 0, 0);
    profiler.stop_tile(timer, 1024, 2048);

    assert_eq!(profiler.tile_stats(TileLevel::Macro).count, 1);

    // Reset
    profiler.reset_tile_stats();

    assert_eq!(profiler.tile_stats(TileLevel::Macro).count, 0);
    assert_eq!(profiler.tile_stats(TileLevel::Midi).count, 0);
    assert_eq!(profiler.tile_stats(TileLevel::Micro).count, 0);
}

/// F367: BrickProfiler tile_stats_to_json
#[test]
fn test_f367_tile_stats_json() {
    let mut profiler = BrickProfiler::new();
    profiler.enable_tile_profiling();

    let timer = profiler.start_tile(TileLevel::Macro, 0, 0);
    profiler.stop_tile(timer, 1024, 2048);

    let json = profiler.tile_stats_to_json();
    assert!(json.contains("\"tile_profiling_enabled\":true"));
    assert!(json.contains("\"level\":\"macro\""));
    assert!(json.contains("\"count\":1"));
}

/// F368: all_tile_stats accessor
#[test]
fn test_f368_all_tile_stats() {
    let profiler = BrickProfiler::new();
    let all_stats = profiler.all_tile_stats();

    assert_eq!(all_stats.len(), 3);
    assert_eq!(all_stats[0].level, TileLevel::Macro);
    assert_eq!(all_stats[1].level, TileLevel::Midi);
    assert_eq!(all_stats[2].level, TileLevel::Micro);
}

/// F369: tile_stats_mut mutable access
#[test]
fn test_f369_tile_stats_mut() {
    let mut profiler = BrickProfiler::new();

    // Directly modify tile stats
    profiler.tile_stats_mut(TileLevel::Macro).count = 42;
    assert_eq!(profiler.tile_stats(TileLevel::Macro).count, 42);
}

/// F370: Disabled tile profiling skips recording
#[test]
fn test_f370_disabled_tile_profiling() {
    let mut profiler = BrickProfiler::new();
    // tile_profiling_enabled is false by default

    let timer = profiler.start_tile(TileLevel::Macro, 0, 0);
    profiler.stop_tile(timer, 1024, 2048);

    // Should not have recorded anything
    assert_eq!(profiler.tile_stats(TileLevel::Macro).count, 0);
}

// ========================================================================
// QA Falsification Tests (F371-F378)
// ========================================================================

/// F371: GFLOP/s exact calculation - 1e9 FLOPs in 1 second = 1.0 GFLOP/s
#[test]
fn test_f371_gflops_exact_1e9_in_1s() {
    let mut stats = TileStats::new(TileLevel::Macro);

    // 1 second (1e9 ns), 1e9 FLOPs
    stats.add_sample(1_000_000_000, 1000, 1_000_000_000);

    let gflops = stats.gflops();
    assert!((gflops - 1.0).abs() < 0.001, "Expected 1.0 GFLOP/s, got {}", gflops);
}

/// F372: Arithmetic Intensity exact - 200 FLOPs / 100 bytes = 2.0
/// Note: Our formula is FLOP / (elements * 4), so 50 elements = 200 bytes
#[test]
fn test_f372_ai_exact_200_flops_100_bytes() {
    let mut stats = TileStats::new(TileLevel::Midi);

    // 50 elements * 4 bytes = 200 bytes, 400 FLOPs -> AI = 2.0
    stats.add_sample(1_000_000, 50, 400);

    let ai = stats.arithmetic_intensity();
    assert!((ai - 2.0).abs() < 0.001, "Expected 2.0 FLOP/byte, got {}", ai);
}

/// F373: Hierarchy aggregation - 4 micro tiles in 1 midi tile
#[test]
fn test_f373_hierarchy_aggregation() {
    let mut profiler = BrickProfiler::new();
    profiler.enable_tile_profiling();

    // Record 1 midi tile
    let midi_timer = profiler.start_tile(TileLevel::Midi, 0, 0);
    profiler.stop_tile(midi_timer, 1024, 2048);

    // Record 4 micro tiles
    for i in 0..4 {
        let micro_timer = profiler.start_tile(TileLevel::Micro, i, 0);
        profiler.stop_tile(micro_timer, 256, 512);
    }

    assert_eq!(profiler.tile_stats(TileLevel::Micro).count, 4, "Expected 4 micro tiles");
    assert_eq!(profiler.tile_stats(TileLevel::Midi).count, 1, "Expected 1 midi tile");
}

/// F374: Profiling overhead benchmark - start_tile/stop_tile < 50ns
#[test]
fn test_f374_profiling_overhead() {
    let mut profiler = BrickProfiler::new();
    profiler.enable_tile_profiling();

    // Warmup
    for _ in 0..1000 {
        let timer = profiler.start_tile(TileLevel::Micro, 0, 0);
        profiler.stop_tile(timer, 1, 1);
    }
    profiler.reset_tile_stats();

    // Measure overhead
    let iterations = 10_000;
    let start = std::time::Instant::now();
    for i in 0..iterations {
        let timer = profiler.start_tile(TileLevel::Micro, i as u32, 0);
        profiler.stop_tile(timer, 1, 1);
    }
    let elapsed_ns = start.elapsed().as_nanos() as f64;
    let overhead_ns = elapsed_ns / iterations as f64;

    // Target: < 50ns per start/stop pair (5000ns bound for CI under heavy runner saturation)
    assert!(
        overhead_ns < 5000.0,
        "Profiling overhead too high: {:.1}ns (target < 50ns)",
        overhead_ns
    );
    println!("F374: Profiling overhead = {:.1}ns", overhead_ns);
}

/// F375: Toggle safety - disabled profiling is zero-cost
#[test]
fn test_f375_toggle_safety_zero_cost() {
    let mut profiler = BrickProfiler::new();
    // Profiling is disabled by default

    // Measure overhead when disabled
    let iterations = 100_000;
    let start = std::time::Instant::now();
    for i in 0..iterations {
        let timer = profiler.start_tile(TileLevel::Micro, i as u32, 0);
        profiler.stop_tile(timer, 1, 1);
    }
    let elapsed_ns = start.elapsed().as_nanos() as f64;
    let overhead_ns = elapsed_ns / iterations as f64;

    // Zero stats recorded
    assert_eq!(
        profiler.tile_stats(TileLevel::Micro).count,
        0,
        "Disabled profiling should not record stats"
    );

    // Near-zero overhead (just timer creation)
    assert!(overhead_ns < 1000.0, "Disabled overhead too high: {:.1}ns", overhead_ns);
    println!("F375: Disabled overhead = {:.1}ns", overhead_ns);
}

/// F376: Summary format contains required sections
#[test]
fn test_f376_summary_format_required_sections() {
    let mut profiler = BrickProfiler::new();
    profiler.enable_tile_profiling();

    // Add samples at each level
    for _ in 0..5 {
        let timer = profiler.start_tile(TileLevel::Macro, 0, 0);
        profiler.stop_tile(timer, 1024, 2_000_000);
    }
    for _ in 0..10 {
        let timer = profiler.start_tile(TileLevel::Midi, 0, 0);
        profiler.stop_tile(timer, 256, 500_000);
    }
    for _ in 0..20 {
        let timer = profiler.start_tile(TileLevel::Micro, 0, 0);
        profiler.stop_tile(timer, 64, 100_000);
    }

    let summary = profiler.tile_summary();

    // Required sections
    assert!(summary.contains("macro"), "Summary missing 'macro' section");
    assert!(summary.contains("midi"), "Summary missing 'midi' section");
    assert!(summary.contains("micro"), "Summary missing 'micro' section");
    assert!(summary.contains("GFLOP/s"), "Summary missing 'GFLOP/s' column");
}

/// F377: JSON schema validation
#[test]
fn test_f377_json_schema_valid() {
    let mut profiler = BrickProfiler::new();
    profiler.enable_tile_profiling();

    let timer = profiler.start_tile(TileLevel::Macro, 0, 0);
    profiler.stop_tile(timer, 1024, 2048);

    let json = profiler.tile_stats_to_json();

    // Parse as JSON
    let parsed: serde_json::Value = serde_json::from_str(&json).expect("Invalid JSON");

    // Required fields
    assert!(parsed["tile_profiling_enabled"].is_boolean());
    assert!(parsed["tiles"].is_array());

    let tiles = parsed["tiles"].as_array().unwrap();
    assert!(!tiles.is_empty(), "tiles array should not be empty");

    let tile = &tiles[0];
    assert!(tile["level"].is_string());
    assert!(tile["count"].is_number());
    assert!(tile["total_ns"].is_number());
    assert!(tile["avg_us"].is_number());
    assert!(tile["gflops"].is_number());
    assert!(tile["arithmetic_intensity"].is_number());
}

/// F378: Demo output verification - Q4K MatVec shows realistic AI
#[test]
fn test_f378_q4k_matvec_realistic_ai() {
    use crate::tiling::{TiledQ4KMatvec, Q4K_SUPERBLOCK_BYTES};

    let mut profiler = BrickProfiler::new();
    profiler.enable_tile_profiling();

    let matvec = TiledQ4KMatvec::new(1024, 1024);
    let weights = vec![0u8; matvec.total_superblocks() * Q4K_SUPERBLOCK_BYTES];
    let input = vec![1.0f32; 1024];
    let mut output = vec![0.0f32; 1024];

    // Profile MatVec execution
    let timer = profiler.start_tile(TileLevel::Macro, 0, 0);
    matvec.execute_scalar(&weights, &input, &mut output);
    let flops = (1024 * 1024 * 2) as u64; // 2 ops per element
    profiler.stop_tile(timer, (1024 * 1024) as u64, flops);

    let stats = profiler.tile_stats(TileLevel::Macro);

    // Q4K MatVec is memory-bound, AI should be low (< 1.0)
    let ai = stats.arithmetic_intensity();
    assert!(ai > 0.0 && ai < 10.0, "Q4K MatVec AI should be low (memory-bound), got {}", ai);

    // Should have non-zero GFLOP/s
    let gflops = stats.gflops();
    assert!(gflops > 0.0, "GFLOP/s should be positive, got {}", gflops);
}
