from __future__ import annotations

from typing import TypedDict

from gungi.fen import ParsedFEN, encode_fen
from gungi.gungi import Gungi
from gungi.utils import ARATA, Board, Color, HandPiece, Piece, PieceType, piece


def create_empty_board() -> Board:
    return [[[None] for _ in range(9)] for _ in range(9)]


class TowerPiece(TypedDict):
    type: PieceType
    color: Color


def place_tower(board: Board, square: str, pieces: list[TowerPiece]) -> None:
    rank, file = map(int, square.split("-"))
    board[rank - 1][9 - file] = [
        Piece(square=square, tier=index + 1, type=tower_piece["type"], color=tower_piece["color"])
        for index, tower_piece in enumerate(pieces)
    ]


def get_targets(gungi: Gungi, square: str) -> list[str]:
    return [move.to[:-2] for move in gungi.moves(square=square, verbose=True)]


def get_san_moves(gungi: Gungi) -> list[str]:
    return gungi.moves()


def build_fen(board: Board, hand: list[HandPiece], turn: Color = "w") -> str:
    return encode_fen(
        ParsedFEN(
            board=board,
            hand=hand,
            turn=turn,
            mode="advanced",
            drafting={"b": False, "w": False},
            move_number=1,
        )
    )


def test_black_end_draft_finishes_draft_immediately():
    gungi = Gungi(
        "9/9/9/9/9/9/9/9/4M4 G1I1J2W2N3R2S2F2D4C1A2K1T1/m1g1i1j2w2n3r2s2f2d4c1a2k1t1 b 3 wb 1"
    )

    black_done_move = next(
        (
            move
            for move in get_san_moves(gungi)
            if move.startswith(f"{ARATA}{piece['marshal']}") and move.endswith("\u7d42")
        ),
        None,
    )
    assert black_done_move is not None

    gungi.move(black_done_move)

    assert gungi.in_draft() is False
    assert gungi.get_drafting_rights() == {"b": False, "w": False}
    assert gungi.turn() == "w"
    assert len(gungi.moves(square="9-5")) > 0


def test_white_end_draft_keeps_black_drafting():
    gungi = Gungi("4m4/9/9/9/9/9/9/9/4M4 D1/d2 w 3 wb 1")

    white_done_move = next((move for move in get_san_moves(gungi) if move.endswith("\u7d42")), None)
    assert white_done_move is not None

    gungi.move(white_done_move)

    assert gungi.in_draft() is True
    assert gungi.get_drafting_rights() == {"b": True, "w": False}
    assert gungi.turn() == "b"
    assert any(not move.endswith("\u7d42") for move in get_san_moves(gungi))


def test_archer_diagonal_blocked_by_higher_wing():
    board = create_empty_board()
    place_tower(board, "5-5", [{"type": piece["archer"], "color": "w"}])
    place_tower(
        board,
        "4-6",
        [
            {"type": piece["soldier"], "color": "b"},
            {"type": piece["warrior"], "color": "b"},
        ],
    )
    place_tower(board, "1-1", [{"type": piece["marshal"], "color": "b"}])
    place_tower(board, "9-9", [{"type": piece["marshal"], "color": "w"}])

    fen = build_fen(board, [], "w")
    gungi = Gungi(fen)

    targets = get_targets(gungi, "5-5")
    assert "3-4" in targets
    assert "3-6" not in targets


def test_archer_diagonal_allowed_when_wing_not_higher():
    board = create_empty_board()
    place_tower(board, "5-5", [{"type": piece["archer"], "color": "w"}])
    place_tower(board, "4-6", [{"type": piece["soldier"], "color": "b"}])
    place_tower(board, "1-1", [{"type": piece["marshal"], "color": "b"}])
    place_tower(board, "9-9", [{"type": piece["marshal"], "color": "w"}])

    fen = build_fen(board, [], "w")
    gungi = Gungi(fen)

    targets = get_targets(gungi, "5-5")
    assert "3-6" in targets


def test_cannon_can_only_leap_forward():
    board = create_empty_board()
    place_tower(
        board,
        "5-5",
        [
            {"type": piece["soldier"], "color": "w"},
            {"type": piece["warrior"], "color": "w"},
            {"type": piece["cannon"], "color": "w"},
        ],
    )
    place_tower(board, "2-5", [{"type": piece["soldier"], "color": "b"}])
    place_tower(board, "5-6", [{"type": piece["soldier"], "color": "b"}])
    place_tower(board, "6-5", [{"type": piece["soldier"], "color": "b"}])
    place_tower(board, "1-1", [{"type": piece["marshal"], "color": "b"}])
    place_tower(board, "9-9", [{"type": piece["marshal"], "color": "w"}])

    fen = build_fen(board, [], "w")
    gungi = Gungi(fen)

    targets = get_targets(gungi, "5-5")
    assert "1-5" in targets
    assert "5-7" not in targets
    assert "7-5" not in targets


def test_archer_can_only_leap_forward():
    board = create_empty_board()
    place_tower(
        board,
        "5-5",
        [
            {"type": piece["soldier"], "color": "w"},
            {"type": piece["warrior"], "color": "w"},
            {"type": piece["archer"], "color": "w"},
        ],
    )
    place_tower(board, "3-5", [{"type": piece["soldier"], "color": "b"}])
    place_tower(board, "6-5", [{"type": piece["soldier"], "color": "b"}])
    place_tower(board, "1-1", [{"type": piece["marshal"], "color": "b"}])
    place_tower(board, "9-9", [{"type": piece["marshal"], "color": "w"}])

    fen = build_fen(board, [], "w")
    gungi = Gungi(fen)

    targets = get_targets(gungi, "5-5")
    assert "1-5" in targets
    assert "7-5" not in targets


def test_musketeer_can_only_leap_forward():
    board = create_empty_board()
    place_tower(
        board,
        "5-5",
        [
            {"type": piece["soldier"], "color": "w"},
            {"type": piece["warrior"], "color": "w"},
            {"type": piece["musketeer"], "color": "w"},
        ],
    )
    place_tower(board, "3-5", [{"type": piece["soldier"], "color": "b"}])
    place_tower(board, "6-6", [{"type": piece["soldier"], "color": "b"}])
    place_tower(board, "1-1", [{"type": piece["marshal"], "color": "b"}])
    place_tower(board, "9-9", [{"type": piece["marshal"], "color": "w"}])

    fen = build_fen(board, [], "w")
    gungi = Gungi(fen)

    targets = get_targets(gungi, "5-5")
    assert "1-5" in targets
    assert "7-7" not in targets


def test_white_arata_tactician_can_betray_friendly_topped_stack():
    board = create_empty_board()
    place_tower(
        board,
        "5-5",
        [
            {"type": piece["soldier"], "color": "b"},
            {"type": piece["warrior"], "color": "w"},
        ],
    )
    place_tower(board, "1-1", [{"type": piece["marshal"], "color": "b"}])
    place_tower(board, "9-9", [{"type": piece["marshal"], "color": "w"}])

    fen = build_fen(
        board,
        [
            HandPiece(type=piece["tactician"], color="w", count=1),
            HandPiece(type=piece["soldier"], color="w", count=1),
        ],
        "w",
    )
    gungi = Gungi(fen)

    san_moves = get_san_moves(gungi)
    expected_move = f"{ARATA}{piece['tactician']}(5-5-3)\u8fd4{piece['soldier']}"
    assert expected_move in san_moves

    gungi.move(expected_move)

    moved_tower = gungi.get("5-5")
    assert [tower_piece.type for tower_piece in moved_tower] == [
        piece["soldier"],
        piece["warrior"],
        piece["tactician"],
    ]
    assert all(tower_piece.color == "w" for tower_piece in moved_tower)
    assert next((hp for hp in gungi.hand("w") if hp.type == piece["tactician"]), None) is None
    assert next((hp for hp in gungi.hand("w") if hp.type == piece["soldier"]), None) is None


def test_black_arata_tactician_can_betray_friendly_topped_stack():
    board = create_empty_board()
    place_tower(
        board,
        "5-5",
        [
            {"type": piece["soldier"], "color": "w"},
            {"type": piece["warrior"], "color": "b"},
        ],
    )
    place_tower(board, "1-1", [{"type": piece["marshal"], "color": "b"}])
    place_tower(board, "9-9", [{"type": piece["marshal"], "color": "w"}])

    fen = build_fen(
        board,
        [
            HandPiece(type=piece["tactician"], color="b", count=1),
            HandPiece(type=piece["soldier"], color="b", count=1),
        ],
        "b",
    )
    gungi = Gungi(fen)

    san_moves = get_san_moves(gungi)
    expected_move = f"{ARATA}{piece['tactician']}(5-5-3)\u8fd4{piece['soldier']}"
    assert expected_move in san_moves

    gungi.move(expected_move)

    moved_tower = gungi.get("5-5")
    assert [tower_piece.type for tower_piece in moved_tower] == [
        piece["soldier"],
        piece["warrior"],
        piece["tactician"],
    ]
    assert all(tower_piece.color == "b" for tower_piece in moved_tower)
    assert next((hp for hp in gungi.hand("b") if hp.type == piece["tactician"]), None) is None
    assert next((hp for hp in gungi.hand("b") if hp.type == piece["soldier"]), None) is None


def test_onboard_tactician_can_betray_friendly_topped_stack():
    gungi = Gungi("4m4/9/9/3|s:D|5/4|f:w:T|4/9/9/9/4M4 D1S1T1/- w 3 - 1")

    san_moves = get_san_moves(gungi)
    tsuke_move = f"{piece['tactician']}(5-5-3)(4-6-3)\u4ed8"
    betray_move = f"{piece['tactician']}(5-5-3)(4-6-3)\u8fd4{piece['spy']}"
    assert tsuke_move in san_moves
    assert betray_move in san_moves

    gungi.move(betray_move)

    moved_tower = gungi.get("4-6")
    assert [tower_piece.type for tower_piece in moved_tower] == [
        piece["spy"],
        piece["soldier"],
        piece["tactician"],
    ]
    assert all(tower_piece.color == "w" for tower_piece in moved_tower)
    assert next((hp for hp in gungi.hand("w") if hp.type == piece["spy"]), None) is None
