use super::helpers::{apply_usi_move, move_from_usi};
use super::*;
use crate::board::zobrist::{Zobrist, ZobristKey};
use crate::types::{Hand, HandPiece, Piece, PieceType, Square};

fn expected_pawn_key(pos: &Position) -> ZobristKey {
    let mut key = Zobrist::no_pawns();
    for (sq, piece) in pos.board_array().iter() {
        if piece.piece_type() == PieceType::PAWN {
            key ^= Zobrist::psq(sq, piece);
        }
    }
    key
}

fn expected_minor_piece_key(pos: &Position) -> ZobristKey {
    let mut key = ZobristKey::default();
    for (sq, piece) in pos.board_array().iter() {
        if piece.piece_type().is_minor() {
            key ^= Zobrist::psq(sq, piece);
        }
    }
    key
}

fn expected_non_pawn_key(pos: &Position, color: Color) -> ZobristKey {
    let mut key = ZobristKey::default();
    for (sq, piece) in pos.board_array().iter() {
        if piece != Piece::NONE && piece.color() == color && piece.piece_type() != PieceType::PAWN {
            key ^= Zobrist::psq(sq, piece);
        }
    }
    key
}

fn expected_material_key(pos: &Position) -> ZobristKey {
    let mut key = ZobristKey::default();
    for (_sq, piece) in pos.board_array().iter() {
        if piece != Piece::NONE {
            key.add(Zobrist::material(piece));
        }
    }
    key
}

fn expected_material_value(pos: &Position) -> i32 {
    const MATERIAL_VALUES: [i32; PieceType::COUNT] =
        [0, 90, 315, 405, 495, 855, 990, 540, 15_000, 540, 540, 540, 540, 945, 1_395, 0];

    let mut value = 0;
    for (_sq, piece) in pos.board_array().iter() {
        if piece != Piece::NONE {
            let sign = if piece.color() == Color::BLACK { 1 } else { -1 };
            value += sign * MATERIAL_VALUES[piece.piece_type().to_index()];
        }
    }

    for color in [Color::BLACK, Color::WHITE] {
        let hand = pos.hand(color);
        let sign = if color == Color::BLACK { 1 } else { -1 };
        for hp in HandPiece::iter() {
            let count = Hand::count_of(hand, hp);
            if count == 0 {
                continue;
            }
            value += sign
                * MATERIAL_VALUES[hp.to_piece_type().to_index()]
                * i32::try_from(count).expect("hand count fits in i32");
        }
    }

    value
}

#[test]
// 通常手適用後のZobrist更新内容が理論値と一致するか検証
fn test_zobrist_after_normal_move_matches_expected() {
    let mut pos = crate::board::hirate_position();

    let initial_zobrist = pos.key();
    let mut board_key = pos.board_key();
    let hand_key = pos.hand_key();

    apply_usi_move(&mut pos, "7g7f");

    assert_ne!(pos.key(), initial_zobrist);

    let from = Square::from_usi("7g").unwrap();
    let to = Square::from_usi("7f").unwrap();
    board_key ^= Zobrist::psq(from, Piece::B_PAWN);
    board_key ^= Zobrist::psq(to, Piece::B_PAWN);
    board_key ^= Zobrist::side();
    let expected = board_key ^ hand_key;

    assert_eq!(pos.key(), expected);
}

#[test]
// 捕獲を含む手でZobrist更新が期待値通りか確認
fn test_zobrist_after_capture_matches_expected() {
    let sfen = "lnsgk1snl/1r4gb1/p1pppp2p/6pp1/1p7/2P6/PP1PPPP1P/1BG4R1/LNS1KGSNL b p 11";
    let mut pos = crate::board::position_from_sfen(sfen).unwrap();

    let initial_zobrist = pos.key();
    let mut board_key = pos.board_key();
    let mut hand_key = pos.hand_key();

    let mv = move_from_usi(&pos, "2h2d");
    let from = mv.from_sq();
    let to = mv.to_sq();
    let moved_piece = pos.piece_on(from);
    let captured_piece = pos.piece_on(to);
    let us = pos.turn();
    assert!(pos.is_legal_move32(mv));
    pos.apply_move32(mv);

    assert_ne!(pos.key(), initial_zobrist);

    let moved_after = if mv.is_promotion() { moved_piece.promote() } else { moved_piece };
    board_key ^= Zobrist::psq(from, moved_piece);
    if captured_piece != Piece::NONE {
        board_key ^= Zobrist::psq(to, captured_piece);
    }
    board_key ^= Zobrist::psq(to, moved_after);
    board_key ^= Zobrist::side();
    if captured_piece != Piece::NONE {
        hand_key.add(Zobrist::hand(us, captured_piece.piece_type().demote(), 1));
    }
    let expected = board_key ^ hand_key;

    assert_eq!(pos.key(), expected);
}

#[test]
// 駒打ち時のZobrist更新が正しく行われるか検証
fn test_zobrist_after_drop_matches_expected() {
    let sfen = "lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPP1PPPP/1B5R1/LNSGKGSNL b P 1";
    let mut pos = crate::board::position_from_sfen(sfen).unwrap();

    let initial_zobrist = pos.key();
    let mut board_key = pos.board_key();
    let mut hand_key = pos.hand_key();

    apply_usi_move(&mut pos, "P*5e");

    assert_ne!(pos.key(), initial_zobrist);

    let to = Square::from_usi("5e").unwrap();
    board_key ^= Zobrist::psq(to, Piece::B_PAWN);
    board_key ^= Zobrist::side();
    hand_key.sub(Zobrist::hand(Color::BLACK, PieceType::PAWN, 1));
    let expected = board_key ^ hand_key;

    assert_eq!(pos.key(), expected);
}

#[test]
// 成りを含む手のZobrist更新が期待通りか確認
fn test_zobrist_after_promote_matches_expected() {
    let sfen = "lnsgk1snl/1p4g2/pR1ppp2p/2p6/9/9/P1SPPPP1P/2G6/LN2KGSNL b B3Prb2p 25";
    let mut pos = crate::board::position_from_sfen(sfen).unwrap();

    let mut board_key = pos.board_key();
    let hand_key = pos.hand_key();

    let mv = move_from_usi(&pos, "8c8f+");
    let from = mv.from_sq();
    let to = mv.to_sq();
    let moved_piece = pos.piece_on(from);
    assert!(pos.is_legal_move32(mv));
    pos.apply_move32(mv);

    assert_ne!(pos.key(), board_key ^ hand_key);

    board_key ^= Zobrist::psq(from, moved_piece);
    board_key ^= Zobrist::psq(to, moved_piece.promote());
    board_key ^= Zobrist::side();
    let expected = board_key ^ hand_key;

    assert_eq!(pos.key(), expected);
}

#[test]
// 異なる手順で同一局面に到達した際Zobristが一致するか確認
fn test_zobrist_is_consistent_for_equivalent_sequences() {
    let mut pos1 = crate::board::hirate_position();

    let from1 = Square::from_file_rank(crate::types::File::FILE_7, crate::types::Rank::RANK_7);
    let to1 = Square::from_file_rank(crate::types::File::FILE_7, crate::types::Rank::RANK_6);
    let piece1 = pos1.piece_on(from1);
    let mv1 = Move32::normal(from1, to1, piece1);
    pos1.apply_move32(mv1);

    let from2 = Square::from_file_rank(crate::types::File::FILE_3, crate::types::Rank::RANK_3);
    let to2 = Square::from_file_rank(crate::types::File::FILE_3, crate::types::Rank::RANK_4);
    let piece2 = pos1.piece_on(from2);
    let mv2 = Move32::normal(from2, to2, piece2);
    pos1.apply_move32(mv2);

    let sfen = "lnsgkgsnl/1r5b1/pppppp1pp/6p2/9/2P6/PP1PPPPPP/1B5R1/LNSGKGSNL b - 3";
    let pos2 = crate::board::position_from_sfen(sfen).unwrap();

    assert_eq!(pos1.key(), pos2.key());
}

#[test]
// 初期局面のZobristがゼロ値にならないことを確認（YaneuraOu互換チェック）
fn test_zobrist_initial_position_is_non_zero() {
    let pos = crate::board::hirate_position();

    assert_ne!(pos.key().low_u64(), 0);
}

#[test]
// 既知の手順でZobristが変化することを確認（YaneuraOu互換チェック）
fn test_zobrist_changes_after_known_sequence() {
    let mut pos = crate::board::hirate_position();
    let initial = pos.key();

    for usi in ["7g7f", "3c3d", "2g2f"] {
        apply_usi_move(&mut pos, usi);
    }

    assert_ne!(pos.key(), initial);
}

#[test]
// Zobristテーブルの特定エントリがゼロでないことを確認
fn test_zobrist_table_sample_entry_is_non_zero() {
    let sq = Square::from_file_rank(crate::types::File::FILE_5, crate::types::Rank::RANK_5);
    let piece = Piece::from_parts(Color::BLACK, PieceType::PAWN);
    let hash = Zobrist::psq(sq, piece);

    assert_ne!(hash.low_u64(), 0);
}

#[test]
// board_key_after() が適用後の board_key() と一致することを確認
fn test_board_key_after_matches_applied_position_for_common_moves() {
    // 通常手
    let mut pos = crate::board::hirate_position();
    let mv = move_from_usi(&pos, "7g7f");
    let expected = pos.board_key_after(mv);
    assert!(pos.is_legal_move32(mv));
    pos.apply_move32(mv);
    assert_eq!(pos.board_key(), expected);

    // 捕獲
    let sfen_capture = "lnsgk1snl/1r4gb1/p1pppp2p/6pp1/1p7/2P6/PP1PPPP1P/1BG4R1/LNS1KGSNL b p 11";
    let mut pos = crate::board::position_from_sfen(sfen_capture).unwrap();
    let mv = move_from_usi(&pos, "2h2d");
    let expected = pos.board_key_after(mv);
    assert!(pos.is_legal_move32(mv));
    pos.apply_move32(mv);
    assert_eq!(pos.board_key(), expected);

    // 駒打ち
    let sfen_drop = "lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPP1PPPP/1B5R1/LNSGKGSNL b P 1";
    let mut pos = crate::board::position_from_sfen(sfen_drop).unwrap();
    let mv = move_from_usi(&pos, "P*5e");
    let expected = pos.board_key_after(mv);
    assert!(pos.is_legal_move32(mv));
    pos.apply_move32(mv);
    assert_eq!(pos.board_key(), expected);

    // 成り
    let sfen_promote = "lnsgk1snl/1p4g2/pR1ppp2p/2p6/9/9/P1SPPPP1P/2G6/LN2KGSNL b B3Prb2p 25";
    let mut pos = crate::board::position_from_sfen(sfen_promote).unwrap();
    let mv = move_from_usi(&pos, "8c8f+");
    let expected = pos.board_key_after(mv);
    assert!(pos.is_legal_move32(mv));
    pos.apply_move32(mv);
    assert_eq!(pos.board_key(), expected);
}

#[test]
fn test_position_key_helpers_match_reference_computation() {
    let sfen = "ln1g3nl/1r3kg2/p2pppsp1/3s2p1p/1pp4P1/P1P1SP2P/1PSPP1P2/2GK3R1/LN3G1NL b Bb 1";
    let pos = crate::board::position_from_sfen(sfen).expect("valid sfen");

    assert_eq!(pos.pawn_key(), expected_pawn_key(&pos));
    assert_eq!(pos.minor_piece_key(), expected_minor_piece_key(&pos));
    assert_eq!(pos.non_pawn_key(Color::BLACK), expected_non_pawn_key(&pos, Color::BLACK));
    assert_eq!(pos.non_pawn_key(Color::WHITE), expected_non_pawn_key(&pos, Color::WHITE));
    assert_eq!(pos.material_key(), expected_material_key(&pos));
    assert_eq!(pos.material_value(), expected_material_value(&pos));
}

#[test]
fn test_position_key_helpers_match_reference_after_sequence() {
    let mut pos = crate::board::hirate_position();

    for usi in ["7g7f", "3c3d", "2g2f", "8c8d", "2f2e", "8d8e", "2e2d", "8e8f", "2d2c+"] {
        apply_usi_move(&mut pos, usi);
    }

    assert_eq!(pos.pawn_key(), expected_pawn_key(&pos));
    assert_eq!(pos.minor_piece_key(), expected_minor_piece_key(&pos));
    assert_eq!(pos.non_pawn_key(Color::BLACK), expected_non_pawn_key(&pos, Color::BLACK));
    assert_eq!(pos.non_pawn_key(Color::WHITE), expected_non_pawn_key(&pos, Color::WHITE));
    assert_eq!(pos.material_key(), expected_material_key(&pos));
    assert_eq!(pos.material_value(), expected_material_value(&pos));
}
