use super::*;

fn mirror_board(board: &BoardArray) -> BoardArray {
    let mut mirrored = BoardArray::empty();
    for (sq, packed) in board.iter() {
        if packed.is_empty() {
            continue;
        }
        mirrored.set(sq.mirror_file(), packed);
    }
    mirrored
}

fn position_from_parts(
    board: BoardArray,
    hands: [Hand; Color::COUNT],
    side_to_move: Color,
    ply: Ply,
) -> Position {
    let mut pos = Position::empty();
    pos.board = board;
    pos.hands = hands;
    pos.set_side_to_move(side_to_move);
    pos.ply = ply;
    pos.rebuild_bitboards();

    let keys = pos.compute_keys();
    pos.board_key = keys.board_key;
    pos.hand_key = keys.hand_key;
    pos.zobrist = pos.board_key ^ pos.hand_key;
    pos
}

#[test]
// PackedSfenの往復でSFENが一致するか確認
fn test_packed_sfen_roundtrip_matches_to_sfen() {
    let cases = [
        "lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL b - 1",
        "l3k2nl/1r1sg1gp1/2np1s2p/p1p1pppR1/1p7/P1PPP1P1P/1PS2P3/2G1GS3/LN1K3NL w BPb 15",
        "l6nl/5+P1gk/2np1S3/p1p4Pp/3P2Sp1/1PPb2P1P/P5GS1/R8/LN4bKL w GR5pnsg 1",
    ];

    for sfen in cases {
        let pos = crate::board::position_from_sfen(sfen).expect("sfen should parse");
        let packed = pos.to_packed_sfen();

        let mut roundtrip = Position::empty();
        roundtrip
            .set_packed_sfen(&packed, false, pos.game_ply())
            .expect("packed sfen should decode");

        assert_eq!(roundtrip.to_sfen(None), pos.to_sfen(None), "PackedSfen roundtrip mismatch");
    }
}

#[test]
// PackedSfenのアンパックがSFEN生成と一致するか確認
fn test_packed_sfen_unpack_matches_to_sfen() {
    let cases = [
        "lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL b - 1",
        "l3k2nl/1r1sg1gp1/2np1s2p/p1p1pppR1/1p7/P1PPP1P1P/1PS2P3/2G1GS3/LN1K3NL w BPb 15",
        "l6nl/5+P1gk/2np1S3/p1p4Pp/3P2Sp1/1PPb2P1P/P5GS1/R8/LN4bKL w GR5pnsg 1",
    ];

    for sfen in cases {
        let pos = crate::board::position_from_sfen(sfen).expect("sfen should parse");
        let packed = pos.to_packed_sfen();
        let unpacked = Position::sfen_unpack(&packed).expect("packed sfen should unpack");
        let expected = position_from_parts(pos.board, pos.hands, pos.turn(), 0);
        assert_eq!(unpacked, expected.to_sfen(None), "PackedSfen unpack mismatch");
    }
}

#[test]
// PackedSfenの左右反転が期待通りか確認
fn test_packed_sfen_mirror_matches_expected() {
    let sfen = "l6nl/5+P1gk/2np1S3/p1p4Pp/3P2Sp1/1PPb2P1P/P5GS1/R8/LN4bKL w GR5pnsg 1";
    let pos = crate::board::position_from_sfen(sfen).expect("sfen should parse");
    let packed = pos.to_packed_sfen();

    let mut mirrored = Position::empty();
    mirrored
        .set_packed_sfen(&packed, true, pos.game_ply())
        .expect("packed sfen should decode with mirror");

    let expected_board = mirror_board(&pos.board);
    let expected = position_from_parts(expected_board, pos.hands, pos.turn(), pos.game_ply());
    assert_eq!(mirrored.to_sfen(None), expected.to_sfen(None), "mirror result mismatch");
}

#[test]
// PSVのサンプルデータに対するPackedSfenの互換性を検証
fn test_packed_sfen_psv_samples_match_cshogi() {
    let samples = [
        PsvSample {
            sfen:
                "2+P1g2nl/3ps1k2/pp2p2p1/3+B1Pp1p/3P2nl1/1P4N2/P2GP1PPP/L4SSK1/+b4G1NL b 2RPgsp 1",
            packed: [
                0x20, 0x93, 0x91, 0x24, 0x0c, 0xab, 0x34, 0x8a, 0x85, 0x5c, 0x97, 0x38, 0x20, 0x70,
                0x3c, 0xef, 0x99, 0x08, 0xa4, 0xaf, 0xf0, 0x50, 0x00, 0x24, 0x01, 0x12, 0x31, 0x7c,
                0xe3, 0xf3, 0xe1, 0xbc,
            ],
            game_ply: 87,
        },
        PsvSample {
            sfen: "ln1g1gsn1/1r1s1k1bl/p2p1p1pp/2p1p1p2/1p6P/2PP5/PPB1PPPP1/2R2KS2/LNSG1G1NL b - 1",
            packed: [
                0x44, 0x1c, 0x63, 0x0a, 0x0c, 0xeb, 0x67, 0x22, 0x2c, 0x27, 0x49, 0x1c, 0xde, 0x44,
                0x3c, 0x48, 0x82, 0xf7, 0x4c, 0x82, 0x07, 0x29, 0x3e, 0x7e, 0x8e, 0xf5, 0x97, 0x14,
                0x96, 0x51, 0x22, 0x0c,
            ],
            game_ply: 17,
        },
        PsvSample {
            sfen: "l8/5+RSbk/p6gp/4pppp1/3P3g1/4SPP1l/P3P1G1b/3+r2SP1/LN3GK2 b SN2P2nl4p 1",
            packed: [
                0x34, 0x01, 0xc9, 0xf8, 0x89, 0xcf, 0x37, 0x5f, 0x82, 0x43, 0x8a, 0xe7, 0xf0, 0x27,
                0x05, 0x0f, 0xd2, 0x11, 0x40, 0xf0, 0x0f, 0x00, 0xc0, 0x32, 0x4a, 0x84, 0x01, 0x28,
                0x83, 0x24, 0x63, 0xad,
            ],
            game_ply: 96,
        },
        PsvSample {
            sfen: "lnkg2g1l/2s1Ps+R2/1p1ppg1pp/p1p2n3/7P1/PPPP3BP/1K2+bPp2/2S2s3/LNG4+RL b N2P 1",
            packed: [
                0x8a, 0xb6, 0x51, 0x12, 0x0c, 0xa4, 0xf8, 0xe0, 0xef, 0xfd, 0x83, 0xc4, 0xf9, 0xae,
                0xc4, 0x49, 0x24, 0xbe, 0x79, 0x25, 0x81, 0x53, 0x0a, 0xc7, 0xb3, 0x4a, 0xc2, 0x32,
                0x92, 0x82, 0x01, 0x28,
            ],
            game_ply: 111,
        },
        PsvSample {
            sfen: "l7l/2gsksg2/ppnpppn2/7rp/2p1S4/1R4P2/P1NPPP1PP/2G1KSG2/L6NL b B2Pb2p 1",
            packed: [
                0x56, 0xa5, 0x91, 0x24, 0x0c, 0xf8, 0x25, 0x2c, 0xde, 0x95, 0xf0, 0x70, 0x26, 0xe2,
                0x20, 0x1d, 0x82, 0x33, 0x11, 0x78, 0x57, 0x69, 0x79, 0x90, 0xfc, 0x60, 0x94, 0x08,
                0x03, 0xf0, 0x20, 0x9f,
            ],
            game_ply: 45,
        },
    ];

    for (idx, sample) in samples.iter().enumerate() {
        let packed_sfen = PackedSfen { data: sample.packed };
        let unpacked = Position::sfen_unpack(&packed_sfen).expect("packed sfen should unpack");
        let pos = crate::board::position_from_sfen(sample.sfen).expect("sample sfen should parse");
        let expected_unpacked = position_from_parts(pos.board, pos.hands, pos.turn(), 0);
        assert_eq!(unpacked, expected_unpacked.to_sfen(None), "PSV unpack mismatch at index {idx}");
        let pos = crate::board::position_from_sfen(sample.sfen).expect("sample sfen should parse");
        let expected = position_from_parts(pos.board, pos.hands, pos.turn(), sample.game_ply);
        let repacked = pos.to_packed_sfen();
        assert_eq!(repacked.data, sample.packed, "PSV repack mismatch at index {idx}");

        let mut roundtrip = Position::empty();
        roundtrip
            .set_packed_sfen(&packed_sfen, false, sample.game_ply)
            .expect("packed sfen should decode");
        assert_eq!(
            roundtrip.to_sfen(None),
            expected.to_sfen(None),
            "PSV set_packed_sfen mismatch at index {idx}"
        );
    }
}

struct PsvSample {
    sfen: &'static str,
    packed: [u8; 32],
    game_ply: u16,
}
