import pytest

from gungi.fen import (
    ADVANCED_POSITION,
    BEGINNER_POSITION,
    INTERMEDIATE_POSITION,
    INTRO_POSITION,
    encode_fen,
    parse_fen,
    validate_fen,
)
from gungi.utils import HandPiece, Piece, piece


def test_parse_intro_position():
    result = parse_fen(INTRO_POSITION)

    assert result.turn == "w"
    assert result.mode == "intro"
    assert result.drafting == {"b": False, "w": False}
    assert result.move_number == 1

    assert len(result.board) == 9
    for rank in result.board:
        assert len(rank) == 9

    assert result.board[0][4][0] == Piece(color="b", square="1-5", tier=1, type=piece["marshal"])
    assert result.board[8][3][0] == Piece(color="w", square="9-6", tier=1, type=piece["general"])


def test_parse_beginner_position():
    result = parse_fen(BEGINNER_POSITION)

    assert result.turn == "w"
    assert result.mode == "beginner"
    assert result.drafting == {"b": False, "w": False}
    assert result.move_number == 1

    assert result.board[1][2][0] == Piece(color="b", square="2-7", tier=1, type=piece["archer"])
    assert result.board[8][4][0] == Piece(color="w", square="9-5", tier=1, type=piece["marshal"])


def test_parse_intermediate_position():
    result = parse_fen(INTERMEDIATE_POSITION)

    assert result.turn == "w"
    assert result.mode == "intermediate"
    assert result.drafting == {"b": True, "w": True}
    assert result.move_number == 1

    for rank in result.board:
        for square in rank:
            assert square[0] is None


def test_parse_advanced_position():
    result = parse_fen(ADVANCED_POSITION)

    assert result.turn == "w"
    assert result.mode == "advanced"
    assert result.drafting == {"b": True, "w": True}
    assert result.move_number == 1

    for rank in result.board:
        for square in rank:
            assert square[0] is None


def test_towers_and_empty_squares():
    fen = "3img3/1sa|a:g:s|1s3/9/9/9/9/9/9/9 M1N3/d2 b 3 b 2"
    result = parse_fen(fen)

    assert result.turn == "b"
    assert result.mode == "advanced"
    assert result.drafting == {"b": True, "w": False}
    assert result.move_number == 2

    tower = result.board[1][3]
    p = Piece(color="b", square="2-6", tier=1, type=piece["archer"])
    assert tower == [
        p,
        Piece(**{**vars(p), "type": piece["general"], "tier": 2}),
        Piece(**{**vars(p), "type": piece["spy"], "tier": 3}),
    ]
    assert result.board[8][8][0] is None


def test_adjacent_towers():
    fen = "3img3/1sa|a:g:s|d:w|k:n|r:w|2/9/9/9/9/9/9/9 M1N3/d2 w 1 w 3"
    result = parse_fen(fen)

    assert result.turn == "w"
    assert result.mode == "beginner"
    assert result.drafting == {"b": False, "w": True}
    assert result.move_number == 3

    tower1 = result.board[1][3]
    p1 = Piece(color="b", square="2-6", tier=1, type=piece["archer"])
    assert tower1 == [
        p1,
        Piece(**{**vars(p1), "type": piece["general"], "tier": 2}),
        Piece(**{**vars(p1), "type": piece["spy"], "tier": 3}),
    ]

    tower2 = result.board[1][4]
    p2 = Piece(color="b", square="2-5", tier=1, type=piece["soldier"])
    assert tower2 == [
        p2,
        Piece(**{**vars(p2), "type": piece["warrior"], "tier": 2}),
    ]

    tower3 = result.board[1][5]
    p3 = Piece(color="b", square="2-4", tier=1, type=piece["musketeer"])
    assert tower3 == [
        p3,
        Piece(**{**vars(p3), "type": piece["lancer"], "tier": 2}),
    ]

    tower4 = result.board[1][6]
    p4 = Piece(color="b", square="2-3", tier=1, type=piece["rider"])
    assert tower4 == [
        p4,
        Piece(**{**vars(p4), "type": piece["warrior"], "tier": 2}),
    ]

    assert result.board[0][0][0] is None
    assert result.board[8][8][0] is None


def test_parse_hand_pieces():
    fen = "3img3/1sa1n1as1/d1fwdwf1d/9/9/9/D1FWDWF1D/1SA1N1AR1/3GMI3 J2N2S1R1D1/j2n2s1r1d1 w 1 - 1"
    result = parse_fen(fen)

    assert len(result.hand) == 10
    assert result.hand[0] == HandPiece(type=piece["major_general"], color="w", count=2)
    assert result.hand[1] == HandPiece(type=piece["lancer"], color="w", count=2)
    assert result.hand[5] == HandPiece(type=piece["major_general"], color="b", count=2)
    assert result.hand[9] == HandPiece(type=piece["soldier"], color="b", count=1)


def test_empty_hands_parse():
    fen = "9/9/9/9/9/9/9/9/9 -/- w 0 - 1"
    result = parse_fen(fen)

    assert result.hand == []
    assert result.turn == "w"
    assert result.mode == "intro"
    assert result.drafting == {"b": False, "w": False}
    assert result.move_number == 1


def test_only_black_hand():
    fen = "3img3/9/9/9/9/9/9/9/9 -/m1r3 b 1 - 1"
    result = parse_fen(fen)

    assert result.turn == "b"
    assert result.mode == "beginner"
    assert result.drafting == {"b": False, "w": False}
    assert result.move_number == 1

    assert len(result.board) == 9
    for rank in result.board:
        assert len(rank) == 9

    assert len(result.hand) == 2
    assert HandPiece(color="b", type=piece["marshal"], count=1) in result.hand
    assert HandPiece(color="b", type=piece["rider"], count=3) in result.hand


def test_only_white_hand():
    fen = "3img3/9/9/9/9/9/9/9/9 M1D3/- w 1 - 1"
    result = parse_fen(fen)

    assert result.turn == "w"
    assert result.mode == "beginner"
    assert result.drafting == {"b": False, "w": False}
    assert result.move_number == 1

    assert len(result.board) == 9
    for rank in result.board:
        assert len(rank) == 9

    assert len(result.hand) == 2
    assert HandPiece(color="w", type=piece["marshal"], count=1) in result.hand
    assert HandPiece(color="w", type=piece["soldier"], count=3) in result.hand


def test_round_trip_encode_decode():
    fen = "3img3/1ra1n1as1/d1fwdwf1d/9/9/9/D1FWDWF1D/1SA1N1AR1/3GMI3 J2N2S1R1D1/j2n2s1r1d1 w 1 - 1"
    parsed = parse_fen(fen)
    encoded = encode_fen(parsed)
    assert encoded == fen


def test_validate_fen():
    assert validate_fen(BEGINNER_POSITION)["ok"] is True


@pytest.mark.parametrize(
    "fen, message",
    [
        ("3img3/1ra1n1xas1/d1fwdwf1d/9/9/9/9/9 J2N2R1D1/j2n2r2d1 w 1 - 1", "expected 9 ranks"),
        ("3img3/1ra1n1as1/d1fwdwf1d w 1 -", "expected 6 fields"),
        ("3img3/1ra1n1xas1/d1fwdwf1d/9/9/9/9/9/9 J2N2R1D1/j2n2r2d1 w 1 - 1", "invalid piece"),
        ("3img3/1ra1n1a1/d1fwdwf1d/9/9/9/9/9/9 J2N2R1D1/j2n2r2d1 w 1 - 1", "expected 9 squares"),
        ("3img3/1ra1n1as1/d1fw|d:f:g:c|wf1d/9/9/9/9/9/9 J2N2R1D1/j2n2r2d1 w 1 - 1", "tower size"),
        ("9/9/9/9/9/9/9/9/9 J2X2R1D1/j2n2r2d1 w 1 - 1", "invalid piece"),
        ("9/9/9/9/9/9/9/9/9 -/- x 0 - 1", "expected 'w' or 'b'"),
        ("9/9/9/9/9/9/9/9/9 -/- w 4 - 1", "expected '0', '1', '2', or '3'"),
        ("9/9/9/9/9/9/9/9/9 -/- w 0 x 1", "drafting availability"),
        ("9/9/9/9/9/9/9/9/9 -/- w 0 - x", "expected an integer"),
    ],
)
def test_validate_fen_errors(fen: str, message: str):
    result = validate_fen(fen)
    assert result["ok"] is False
    assert message in str(result["error"])
