use std::path::Path;

use serde::Deserialize;
use rsstacker::engine::board::BOARD_ROWS;
use rsstacker::replay::load_recording;
use rsstacker::sim::{ReplaySimulator, simulate};

#[derive(Deserialize)]
struct SimResult {
    rel: String,
    sim_pieces: u32,
    sim_lines: u32,
    clear_mix: std::collections::HashMap<String, u32>,
    lock_history: Vec<(char, u8, i32, i32, u32)>,
}

#[derive(Deserialize)]
struct WestlFrame {
    frame: i32,
    pieces: u32,
    lines: u32,
    grid: Vec<String>,
}

#[derive(Deserialize)]
struct Westl {
    rel: String,
    frames: Vec<WestlFrame>,
}

#[derive(Deserialize)]
struct SimFixture {
    results: Vec<SimResult>,
    westl: Westl,
}

fn load_fixture() -> SimFixture {
    let raw = std::fs::read_to_string("tests/fixtures/sim.json").unwrap();
    serde_json::from_str(&raw).unwrap()
}

#[test]
fn simulate_matches_fixture_results() {
    let fixture = load_fixture();
    assert_eq!(fixture.results.len(), 4);
    for result in &fixture.results {
        let r = load_recording(Path::new(&format!("tests/data/{}", result.rel))).unwrap();
        let board = simulate(&r);
        assert_eq!(
            board.pieces_placed, result.sim_pieces,
            "pieces {}",
            result.rel
        );
        assert_eq!(
            board.lines_cleared, result.sim_lines,
            "lines {}",
            result.rel
        );
        let mut mix = std::collections::HashMap::new();
        for &(.., cleared) in &board.lock_history {
            if (1u32..=4u32).contains(&cleared) {
                *mix.entry(cleared.to_string()).or_insert(0u32) += 1;
            }
        }
        for k in ["1", "2", "3", "4"] {
            let got = *mix.get(k).unwrap_or(&0);
            let exp = *result.clear_mix.get(k).unwrap_or(&0);
            assert_eq!(got, exp, "clear_mix[{k}] {}", result.rel);
        }
        assert_eq!(
            board.lock_history, result.lock_history,
            "lock_history {}",
            result.rel
        );
    }
}

#[test]
fn simulate_matches_fixture_frames() {
    let fixture = load_fixture();
    let r = load_recording(Path::new(&format!("tests/data/{}", fixture.westl.rel))).unwrap();
    let mut sim = ReplaySimulator::new(&r, 300);
    let mut frame_index = 0usize;
    while let Some(frame) = sim.step() {
        let expected = &fixture.westl.frames[frame_index];
        assert_eq!(frame, expected.frame, "frame number at index {frame_index}");
        assert_eq!(
            sim.board().pieces_placed,
            expected.pieces,
            "pieces at frame {frame}"
        );
        assert_eq!(
            sim.board().lines_cleared,
            expected.lines,
            "lines at frame {frame}"
        );
        let got: Vec<String> = (0..BOARD_ROWS)
            .map(|row| sim.board().row_string(row))
            .collect();
        assert_eq!(got, expected.grid, "grid at frame {frame}");
        frame_index += 1;
    }
    assert_eq!(frame_index, fixture.westl.frames.len(), "frame count");
}
