import re

from gungi.fen import BEGINNER_POSITION, parse_fen
from gungi.gungi import Gungi
from gungi.piece_ids import assign_piece_ids_with_state
from gungi.utils import ARATA, piece


def _kanji_pattern() -> str:
    glyphs = "".join(piece.values())
    return f"[{glyphs}]"


def test_id_assignment_beginner():
    gungi = Gungi(BEGINNER_POSITION)
    soldier = gungi.get_top("7-5")
    assert soldier and soldier.id
    assert re.match(rf"^1-w-{piece['soldier']}-\d+$", soldier.id)

    hand_piece = gungi.hand("w")[0]
    assert hand_piece.id
    assert re.match(rf"^1-w-{_kanji_pattern()}-\d+$", hand_piece.id)


def test_deterministic_ids_same_position():
    g1 = Gungi(BEGINNER_POSITION)
    g2 = Gungi(BEGINNER_POSITION)

    assert g1.get_top("7-5").id == g2.get_top("7-5").id  # type: ignore[union-attr]
    assert g1.hand("w")[0].id == g2.hand("w")[0].id


def test_move_preserves_id():
    gungi = Gungi(BEGINNER_POSITION)
    initial = gungi.get_top("7-5")
    initial_id = initial.id
    gungi.move(f"{piece['soldier']}(7-5-1)(6-5-1)")
    moved = gungi.get_top("6-5")
    assert moved and moved.id == initial_id
    assert gungi.get_top("7-5") is None


def test_arata_board_inherits_hand_id():
    gungi = Gungi(BEGINNER_POSITION)
    initial_hand = next(hp for hp in gungi.hand("w") if hp.type == piece["major_general"])
    initial_id = initial_hand.id

    gungi.move(f"{ARATA}{piece['major_general']}(7-2-1)")
    placed = gungi.get_top("7-2")
    assert placed and placed.id == initial_id

    new_hand = next(hp for hp in gungi.hand("w") if hp.type == piece["major_general"])
    assert new_hand.id and new_hand.id != initial_id


def test_ids_through_multiple_moves():
    gungi = Gungi(BEGINNER_POSITION)
    soldier_id = gungi.get_top("7-5").id

    gungi.move(f"{piece['soldier']}(7-5-1)(6-5-1)")
    assert gungi.get_top("6-5").id == soldier_id

    gungi.move(f"{piece['soldier']}(3-5-1)(4-5-1)")

    gungi.move(f"{piece['soldier']}(6-5-1)(5-5-1)")
    assert gungi.get_top("5-5").id == soldier_id


def test_load_vs_move_consistency():
    start = BEGINNER_POSITION
    end = (
        "3img3/1ra1n1as1/d1fwdwf1d/9/9/4D4/D1FW1WF1D/1SA1N1AR1/3GMI3 J2N2S1R1D1/j2n2s1r1d1 b 1 - 1"
    )

    g1 = Gungi(start)
    initial_piece_id1 = g1.get_top("7-5").id
    g1.move(f"{piece['soldier']}(7-5-1)(6-5-1)")
    moved_id1 = g1.get_top("6-5").id

    g2 = Gungi(start)
    initial_piece_id2 = g2.get_top("7-5").id
    g2.load(end)
    moved_id2 = g2.get_top("6-5").id

    assert initial_piece_id1 == initial_piece_id2
    assert moved_id1 == moved_id2
    assert moved_id1 == initial_piece_id1


def test_arata_load_vs_move_consistency():
    start = BEGINNER_POSITION
    end = "3img3/1ra1n1as1/d1fwdwf1d/9/9/9/D1FWDWFJD/1SA1N1AR1/3GMI3 J1N2S1R1D1/j2n2s1r1d1 b 1 - 1"

    g1 = Gungi(start)
    initial_hand_id1 = next(hp for hp in g1.hand("w") if hp.type == piece["major_general"]).id
    g1.move(f"{ARATA}{piece['major_general']}(7-2-1)")
    placed1 = g1.get_top("7-2").id
    new_hand_id1 = next(hp for hp in g1.hand("w") if hp.type == piece["major_general"]).id

    g2 = Gungi(start)
    initial_hand_id2 = next(hp for hp in g2.hand("w") if hp.type == piece["major_general"]).id
    g2.load(end)
    placed2 = g2.get_top("7-2").id
    new_hand_id2 = next(hp for hp in g2.hand("w") if hp.type == piece["major_general"]).id

    assert initial_hand_id1 == initial_hand_id2
    assert placed1 == placed2 == initial_hand_id1
    assert new_hand_id1 == new_hand_id2
    assert new_hand_id1 != initial_hand_id1


def test_sequential_loads_vs_moves():
    fen1 = BEGINNER_POSITION
    fen2 = (
        "3img3/1ra1n1as1/d1fwdwf1d/9/9/4D4/D1FW1WF1D/1SA1N1AR1/3GMI3 J2N2S1R1D1/j2n2s1r1d1 b 1 - 1"
    )
    fen3 = "3img3/1ra1n1as1/d1fw1wf1d/4d4/9/4D4/D1FW1WF1D/1SA1N1AR1/3GMI3 J2N2S1R1D1/j2n2s1r1d1 w 1 - 2"

    g1 = Gungi(fen1)
    white_id1 = g1.get_top("7-5").id
    black_id1 = g1.get_top("3-5").id
    g1.move(f"{piece['soldier']}(7-5-1)(6-5-1)")
    g1.move(f"{piece['soldier']}(3-5-1)(4-5-1)")
    final_white1 = g1.get_top("6-5").id
    final_black1 = g1.get_top("4-5").id

    g2 = Gungi(fen1)
    white_id2 = g2.get_top("7-5").id
    black_id2 = g2.get_top("3-5").id
    g2.load(fen2)
    g2.load(fen3)
    final_white2 = g2.get_top("6-5").id
    final_black2 = g2.get_top("4-5").id

    assert white_id1 == white_id2
    assert black_id1 == black_id2
    assert final_white1 == final_white2 == white_id1
    assert final_black1 == final_black2 == black_id1


def test_non_sequential_loads_reset_ids():
    gungi = Gungi(BEGINNER_POSITION)
    initial_id = gungi.get_top("7-5").id

    gungi.load("9/9/9/9/4d4/9/9/9/9 -/- w 0 - 1")
    gungi.load("9/9/9/9/9/9/9/9/4D4 -/- w 0 - 1")
    new_id = gungi.get_top("9-5").id
    assert initial_id != new_id


def test_reset_preserves_canonical_ids():
    gungi = Gungi(BEGINNER_POSITION)
    initial_id = gungi.get_top("7-5").id
    gungi.move(f"{piece['soldier']}(7-5-1)(6-5-1)")
    gungi.reset()
    reset_id = gungi.get_top("7-5").id
    assert reset_id == initial_id


def test_id_format_and_uniqueness():
    gungi = Gungi(BEGINNER_POSITION)
    pattern = re.compile(rf"^1-[wb]-{_kanji_pattern()}-\d+$")

    piece_sample = gungi.get_top("7-5")
    assert piece_sample.id and pattern.match(piece_sample.id)

    hand_piece = gungi.hand("w")[0]
    assert hand_piece.id and pattern.match(hand_piece.id)

    white_soldiers = []
    for rank in range(1, 10):
        for file in range(1, 10):
            board_piece = gungi.get_top(f"{rank}-{file}")
            if board_piece and board_piece.type == piece["soldier"] and board_piece.color == "w":
                white_soldiers.append(board_piece.id)
    assert len(set(white_soldiers)) == len(white_soldiers)


def test_duplicate_ids_are_fixed():
    test_fen = "9/9/9/D3D1D2/9/9/9/9/9 -/- w 1 - 1"
    state = parse_fen(test_fen)
    soldiers = [
        board_piece
        for rank in state.board
        for file in rank
        for board_piece in file
        if board_piece and board_piece.type == piece["soldier"] and board_piece.color == "w"
    ]

    soldiers[0].id = "1-w-\u5175-1"
    soldiers[1].id = "1-w-\u5175-1"
    soldiers[2].id = "1-w-\u5175-2"

    fixed_state = assign_piece_ids_with_state(test_fen)
    fixed_soldiers = [
        board_piece.id or ""
        for rank in fixed_state.board
        for file in rank
        for board_piece in file
        if board_piece and board_piece.type == piece["soldier"] and board_piece.color == "w"
    ]

    assert len(set(fixed_soldiers)) == len(fixed_soldiers)
    assert all(id_ for id_ in fixed_soldiers)
    assert all(re.match(r"^1-w-\u5175-\d+$", id_) for id_ in fixed_soldiers)
