import chess

from ..base import BaseDetector
from ...context import MoveContext
from ...i18n import format_piece_square
from ...utils import (
    get_piece_value,
    cast_ray,
    find_relative_pin,
    RAY_PIECE_TYPES,
    BISHOP_DIRECTIONS,
    ROOK_DIRECTIONS,
    QUEEN_DIRECTIONS,
)


class PinDetector(BaseDetector):
    """
    Детектор связки.

    Связка — фигура атакует вражескую фигуру, за которой стоит более ценная
    фигура или король. Связанная фигура не может уйти без потери.

    Типы связок:
    - Абсолютная: связанная фигура прикрывает короля (не может ходить вообще)
    - Относительная: за связанной стоит ценная фигура (может ходить, но невыгодно)
    """

    key = "pin"

    def __init__(self):
        self._pinned_sq: int | None = None
        self._behind_sq: int | None = None

    def detect(self, ctx: MoveContext) -> bool:
        self._pinned_sq = None
        self._behind_sq = None

        piece = ctx.moved_piece

        if piece is None:
            return False

        # Только лучевые фигуры могут создавать связку
        if piece.piece_type not in RAY_PIECE_TYPES:
            return False

        to_sq = ctx.move.to_square
        defender_color = ctx.opponent_side

        # Выбираем направления по типу фигуры
        if piece.piece_type == chess.BISHOP:
            directions = BISHOP_DIRECTIONS
        elif piece.piece_type == chess.ROOK:
            directions = ROOK_DIRECTIONS
        else:  # QUEEN
            directions = QUEEN_DIRECTIONS

        # Проверяем каждое направление
        for direction in directions:
            pieces_on_ray = cast_ray(ctx.board_after, to_sq, direction, limit=2)

            # Нужно минимум 2 фигуры на луче
            if len(pieces_on_ray) < 2:
                continue

            first_sq, first_piece = pieces_on_ray[0]
            second_sq, second_piece = pieces_on_ray[1]

            # Обе фигуры должны быть противника
            if first_piece.color != defender_color:
                continue
            if second_piece.color != defender_color:
                continue

            first_value = ctx.get_piece_value_at(first_sq)
            second_value = ctx.get_piece_value_at(second_sq)

            # Абсолютная связка (на короля)
            is_absolute_pin = second_piece.piece_type == chess.KING

            if is_absolute_pin:
                attacker_value = get_piece_value(piece.piece_type)

                # Даже в абсолютной связке: связанная фигура может взять
                # связывающую (двигаясь по линии связки)
                if to_sq in ctx.board_after.attacks(first_sq):
                    pinner_defended = ctx.board_after.is_attacked_by(
                        ctx.moving_side, to_sq
                    )
                    if not pinner_defended or first_value <= attacker_value:
                        continue  # Связанная берёт связывающую без потерь

                self._pinned_sq = first_sq
                self._behind_sq = second_sq
                return True

            # Относительная связка (вторая фигура дороже первой)
            if second_value > first_value:
                # Проверяем: если наша фигура висит — противник может взять её
                # и связка не работает
                if ctx.moved_piece_is_hanging:
                    continue  # Проверяем следующее направление

                attacker_value = get_piece_value(piece.piece_type)

                # Может ли связанная фигура просто взять связывающую?
                # Если да и размен не проигрышный — связка не работает
                if to_sq in ctx.board_after.attacks(first_sq):
                    pinner_defended = ctx.board_after.is_attacked_by(
                        ctx.moving_side, to_sq
                    )
                    if not pinner_defended or first_value <= attacker_value:
                        continue  # Связанная берёт связывающую без потерь

                # Если фигура сзади защищена — связка выгодна только если
                # атакующий дешевле фигуры сзади (иначе нет выигрыша при пробитии)
                second_is_defended = ctx.board_after.is_attacked_by(
                    defender_color, second_sq
                )
                if second_is_defended and attacker_value >= second_value:
                    continue  # Невыгодно пробивать связку

                self._pinned_sq = first_sq
                self._behind_sq = second_sq
                return True

            # Связка равноценных фигур (не пешки):
            # осмысленна, если связанная фигура атакована ещё одной нашей фигурой
            if second_value == first_value and first_value > 1:
                if ctx.moved_piece_is_hanging:
                    continue

                attacker_value = get_piece_value(piece.piece_type)

                # Может ли связанная взять связывающую без потерь?
                if to_sq in ctx.board_after.attacks(first_sq):
                    pinner_defended = ctx.board_after.is_attacked_by(
                        ctx.moving_side, to_sq
                    )
                    if not pinner_defended or first_value <= attacker_value:
                        continue

                # Есть ли другая наша фигура, атакующая связанную?
                our_attackers = ctx.board_after.attackers(
                    ctx.moving_side, first_sq
                )
                other_attackers = our_attackers & ~chess.BB_SQUARES[to_sq]
                if not other_attackers:
                    continue  # Нет доп. давления — связка не осмысленна

                # Если цель уже в относительной связке другой нашей фигуры —
                # это тема exploiting_pin, не pin
                rel_pin = find_relative_pin(
                    ctx.board_after, first_sq, ctx.moving_side,
                    exclude_sq=to_sq
                )
                if rel_pin is not None:
                    continue

                self._pinned_sq = first_sq
                self._behind_sq = second_sq
                return True

        return False

    def format_details(self, ctx: MoveContext, lang: str) -> str:
        if self._pinned_sq is None or self._behind_sq is None:
            return ""
        pinner = ctx.board_after.piece_at(ctx.move.to_square)
        pinned = ctx.board_after.piece_at(self._pinned_sq)
        behind = ctx.board_after.piece_at(self._behind_sq)
        if not pinner or not pinned or not behind:
            return ""
        p = format_piece_square(pinner, ctx.move.to_square, lang)
        n = format_piece_square(pinned, self._pinned_sq, lang)
        b = format_piece_square(behind, self._behind_sq, lang)
        return f"{p} \u2192 {n}/{b}"

    def get_arrows(self, ctx: MoveContext) -> list[tuple[int, int]]:
        if self._pinned_sq is None or self._behind_sq is None:
            return []
        to_sq = ctx.move.to_square
        return [
            (to_sq, self._pinned_sq),
            (self._pinned_sq, self._pinned_sq),
            (to_sq, self._behind_sq),
        ]
