"""Practice screen — play an opening against Stockfish with book feedback."""

from __future__ import annotations

from typing import TYPE_CHECKING

import chess
import chess.engine
from rich.text import Text
from textual.app import ComposeResult
from textual.screen import Screen
from textual.widgets import Static, Footer, Header, Input, Button
from textual.containers import Horizontal, Vertical
from textual import work

from chess_cli.game import GameState
from chess_cli.openings.models import Opening, OpeningNode
from chess_cli.tui.widgets.board import BoardWidget, compute_captured
from chess_cli.tui.widgets.eval_bar import EvalBarWidget
from chess_cli.tui.widgets.move_list import MoveListWidget
from chess_cli.tui.widgets.player_info import PlayerInfoWidget

if TYPE_CHECKING:
    from chess_cli.engine import EnginePlayer

CAPTURE_SYMBOLS = {
    chess.QUEEN: "\u265b",
    chess.ROOK: "\u265c",
    chess.BISHOP: "\u265d",
    chess.KNIGHT: "\u265e",
    chess.PAWN: "\u265f",
}


class PracticeScreen(Screen):
    """Play against Stockfish with opening book feedback."""

    DEFAULT_CSS = """
PracticeScreen {
    layout: vertical;
}

#opponent-info {
    height: auto;
    margin: 0 1;
}

#player-info {
    height: auto;
    margin: 0 1;
}

#board-area {
    height: 1fr;
    min-height: 14;
    margin: 1 1;
}

#eval-bar {
    width: 4;
    margin-right: 1;
}

#board {
    width: 28;
    height: 10;
}

#move-panel {
    width: 1fr;
    min-width: 22;
    height: 100%;
    margin-left: 2;
    border: round $primary-background;
    overflow-y: auto;
}

#move-panel-title {
    text-align: center;
    text-style: bold;
    color: $text-muted;
    width: 100%;
    height: 1;
    padding: 1 0 0 0;
}

#book-feedback {
    width: 100%;
    height: 2;
    padding: 1 1;
}

#move-list {
    width: 100%;
    height: 1fr;
    overflow-y: auto;
}

#action-bar {
    height: 3;
    margin: 1 2;
}

#status-line {
    width: 1fr;
    height: 3;
    content-align: left middle;
    color: $error;
    padding: 0 1;
}

#btn-undo {
    width: auto;
    min-width: 14;
}

#btn-undo.hidden {
    display: none;
}

#move-input {
    margin: 0 2 0 2;
}
"""

    def __init__(
        self,
        opening: Opening,
        difficulty: str = "intermediate",
        threads: int = 1,
        hash_mb: int = 16,
        move_time: float | None = None,
        show_undo_button: bool = False,
    ) -> None:
        self.opening = opening
        self.player_color = opening.color
        self.difficulty = difficulty
        self.threads = threads
        self.hash_mb = hash_mb
        self.move_time = move_time
        self._show_undo_button = show_undo_button
        self.game: GameState | None = None
        self.engine: EnginePlayer | None = None

        # Book tracking: walk the opening tree
        self._current_node: OpeningNode | None = opening.root
        self._in_book = True
        super().__init__()

    def compose(self) -> ComposeResult:
        yield Header(show_clock=False)
        yield PlayerInfoWidget(id="opponent-info")
        with Horizontal(id="board-area"):
            yield EvalBarWidget(id="eval-bar")
            yield BoardWidget(id="board")
            with Vertical(id="move-panel"):
                yield Static(f"Practice: {self.opening.name}", id="move-panel-title")
                yield Static("", id="book-feedback")
                yield MoveListWidget(id="move-list")
        yield PlayerInfoWidget(id="player-info")
        with Horizontal(id="action-bar"):
            yield Static("", id="status-line")
            yield Button("\u21a9 Undo", id="btn-undo")
        yield Input(placeholder="Your move (or 'help')...", id="move-input")
        yield Footer()

    # ------------------------------------------------------------------
    # Lifecycle
    # ------------------------------------------------------------------

    def on_mount(self) -> None:
        if not self._show_undo_button:
            self.query_one("#btn-undo", Button).add_class("hidden")
        self.query_one("#move-input", Input).focus()
        self._start_engine()

    def on_unmount(self) -> None:
        self.workers.cancel_group(self, "engine")
        if self.engine:
            self.engine.quit()
            self.engine = None

    # ------------------------------------------------------------------
    # Engine startup
    # ------------------------------------------------------------------

    @work(thread=True, exclusive=True, group="engine")
    def _start_engine(self) -> None:
        self.app.call_from_thread(self._set_status, "Starting engine...")
        try:
            from chess_cli.engine import EnginePlayer

            engine = EnginePlayer(
                difficulty=self.difficulty,
                threads=self.threads,
                hash_mb=self.hash_mb,
                move_time=self.move_time,
            )
            self.app.call_from_thread(self._on_engine_ready, engine)
        except Exception as e:
            self.app.call_from_thread(self._set_status, f"Engine error: {e}")

    def _on_engine_ready(self, engine: EnginePlayer) -> None:
        self.engine = engine
        self.game = GameState(
            player_color=self.player_color, difficulty=self.difficulty
        )
        self.game.opponent_elo = engine.elo
        self._setup_player_info()
        self._update_display()
        self._set_status("")

        # Show initial book hint
        if self._current_node and self.game.is_player_turn():
            self._set_book_feedback(
                f"Book move: play {self._current_node.move}", "dim"
            )

        # If player is Black, engine moves first
        if not self.game.is_player_turn():
            self._request_engine_move()

    # ------------------------------------------------------------------
    # Player info setup
    # ------------------------------------------------------------------

    def _setup_player_info(self) -> None:
        player_name = (
            "You (White)" if self.player_color == chess.WHITE else "You (Black)"
        )
        opponent_name = (
            f"Stockfish ({self.difficulty.capitalize()}, "
            f"{self.game.opponent_elo or 'max'})"
        )

        opp_info = self.query_one("#opponent-info", PlayerInfoWidget)
        opp_info.player_name = opponent_name
        opp_info.is_white = self.player_color == chess.BLACK

        player_info = self.query_one("#player-info", PlayerInfoWidget)
        player_info.player_name = player_name
        player_info.is_white = self.player_color == chess.WHITE

    # ------------------------------------------------------------------
    # Display helpers
    # ------------------------------------------------------------------

    def _update_display(self) -> None:
        if not self.game:
            return

        board_widget = self.query_one("#board", BoardWidget)
        board_widget.board_fen = self.game.board.fen()
        board_widget.perspective = self.player_color == chess.WHITE

        if self.game.move_history:
            last = self.game.move_history[-1]
            board_widget.highlight_from = last.from_square
            board_widget.highlight_to = last.to_square
        else:
            board_widget.highlight_from = -1
            board_widget.highlight_to = -1

        self.query_one("#move-list", MoveListWidget).update_moves(
            self.game.san_history
        )

        white_cap, black_cap = compute_captured(self.game.board)
        opp_info = self.query_one("#opponent-info", PlayerInfoWidget)
        player_info = self.query_one("#player-info", PlayerInfoWidget)

        if self.player_color == chess.WHITE:
            opp_info.captured_text = "".join(
                CAPTURE_SYMBOLS.get(pt, "") for pt in white_cap
            )
            player_info.captured_text = "".join(
                CAPTURE_SYMBOLS.get(pt, "") for pt in black_cap
            )
        else:
            opp_info.captured_text = "".join(
                CAPTURE_SYMBOLS.get(pt, "") for pt in black_cap
            )
            player_info.captured_text = "".join(
                CAPTURE_SYMBOLS.get(pt, "") for pt in white_cap
            )

        is_player_turn = self.game.is_player_turn()
        player_info.is_active = is_player_turn
        opp_info.is_active = not is_player_turn

    def _set_status(self, msg: str) -> None:
        self.query_one("#status-line", Static).update(f"  {msg}" if msg else "")

    def _set_book_feedback(self, msg: str, style: str = "") -> None:
        fb = self.query_one("#book-feedback", Static)
        text = Text()
        text.append(f"  {msg}", style=style)
        fb.update(text)

    def _update_eval(
        self, eval_score: float, is_mate: bool, mate_in: int | None
    ) -> None:
        eval_bar = self.query_one("#eval-bar", EvalBarWidget)
        eval_bar.eval_score = eval_score
        eval_bar.is_mate = is_mate
        eval_bar.mate_in = mate_in or 0
        eval_bar.perspective = self.player_color == chess.WHITE

        display_eval = (
            eval_score if self.player_color == chess.WHITE else -eval_score
        )
        if is_mate and mate_in is not None:
            display_mate = (
                mate_in if self.player_color == chess.WHITE else -mate_in
            )
            eval_text = f"Eval: #{display_mate}"
        else:
            sign = "+" if display_eval >= 0 else ""
            eval_text = f"Eval: {sign}{display_eval:.1f}"
        self.query_one("#opponent-info", PlayerInfoWidget).eval_text = eval_text

    def _disable_input(self) -> None:
        self.query_one("#move-input", Input).disabled = True
        self.query_one("#btn-undo", Button).disabled = True

    def _enable_input(self) -> None:
        inp = self.query_one("#move-input", Input)
        inp.disabled = False
        inp.focus()
        self.query_one("#btn-undo", Button).disabled = False

    # ------------------------------------------------------------------
    # Book tracking
    # ------------------------------------------------------------------

    def _check_player_book_move(self, played_san: str) -> None:
        """Check if the player's move matches the book."""
        if not self._in_book or self._current_node is None:
            return

        expected_san = self._current_node.move
        # Compare by parsing both on the pre-move board to handle notation variants
        # Since the move is already pushed, we need to compare SANs directly
        if played_san == expected_san:
            explanation = self._current_node.explanation
            if explanation:
                self._set_book_feedback(f"Book move \u2713 \u2014 {explanation}", "green")
            else:
                self._set_book_feedback("Book move \u2713", "green")
            # Advance to opponent's response
            if self._current_node.children:
                self._current_node = self._current_node.children[0]
            else:
                self._current_node = None
                self._in_book = False
                self._set_book_feedback("End of book line \u2014 you're on your own!", "dim")
        else:
            self._set_book_feedback(
                f"Deviation \u2014 book suggests {expected_san}", "yellow"
            )
            self._current_node = None
            self._in_book = False

    def _check_engine_book_move(self, engine_san: str) -> None:
        """Advance the book pointer after the engine's move."""
        if not self._in_book or self._current_node is None:
            return

        if engine_san == self._current_node.move:
            # Engine played the book move, advance
            if self._current_node.children:
                self._current_node = self._current_node.children[0]
            else:
                self._current_node = None
                self._in_book = False
        else:
            # Engine deviated
            self._set_book_feedback("Opponent deviated from book", "dim")
            self._current_node = None
            self._in_book = False

    # ------------------------------------------------------------------
    # Input handling
    # ------------------------------------------------------------------

    def on_input_submitted(self, event: Input.Submitted) -> None:
        user_input = event.value.strip()
        event.input.value = ""
        if not user_input or not self.game:
            return

        cmd = user_input.lower().split()

        if cmd[0] in ("help", "h", "?"):
            self.notify(
                "Commands: help, moves, undo, resign, draw, eval, pgn, save, quit\n"
                "Move notation: e4, Nf3, Bb5, O-O, O-O-O, exd5, e8=Q",
                title="Help",
                timeout=10,
            )
            return
        elif cmd[0] in ("quit", "q"):
            self._quit_game()
            return
        elif cmd[0] in ("undo", "u"):
            self._handle_undo()
            return
        elif cmd[0] == "resign":
            self._handle_resign()
            return
        elif cmd[0] == "moves":
            legal = sorted(
                self.game.board.san(m) for m in self.game.board.legal_moves
            )
            self.notify(", ".join(legal), title="Legal moves", timeout=10)
            return
        elif cmd[0] == "draw":
            self._handle_draw()
            return
        elif cmd[0] == "eval":
            eval_bar = self.query_one("#eval-bar", EvalBarWidget)
            display_score = (
                eval_bar.eval_score
                if self.player_color == chess.WHITE
                else -eval_bar.eval_score
            )
            if eval_bar.is_mate:
                mate_display = (
                    eval_bar.mate_in
                    if self.player_color == chess.WHITE
                    else -eval_bar.mate_in
                )
                self.notify(f"Evaluation: #{mate_display}", title="Eval")
            else:
                sign = "+" if display_score >= 0 else ""
                self.notify(
                    f"Evaluation: {sign}{display_score:.2f}",
                    title="Eval",
                )
            return
        elif cmd[0] == "pgn":
            if self.game:
                self.notify(
                    str(self.game.to_pgn()), title="PGN", timeout=15
                )
            return
        elif cmd[0] == "save":
            self._handle_save(cmd)
            return

        self._try_move(user_input)

    def on_button_pressed(self, event: Button.Pressed) -> None:
        if event.button.id == "btn-undo":
            self._handle_undo()

    # ------------------------------------------------------------------
    # Move handling
    # ------------------------------------------------------------------

    def _try_move(self, user_input: str) -> None:
        move_input = user_input.replace("0-0-0", "O-O-O").replace("0-0", "O-O")

        # Get the SAN before pushing for book comparison
        try:
            move = self.game.board.parse_san(move_input)
            played_san = self.game.board.san(move)
        except (
            chess.InvalidMoveError,
            chess.IllegalMoveError,
            chess.AmbiguousMoveError,
        ):
            if self._is_promotion_attempt(move_input):
                self._prompt_promotion(move_input)
                return
            self._set_status(
                f"Invalid move: {user_input}. Type 'moves' for legal moves."
            )
            return

        # Push the move
        self.game.make_move(played_san)
        self._set_status("")

        # Check book
        self._check_player_book_move(played_san)

        self._update_display()

        over_msg = self.game.get_game_over_message()
        if over_msg:
            self._show_game_over(over_msg)
            return

        self._disable_input()
        self._after_player_move()

    @work(thread=True, exclusive=True, group="engine")
    def _after_player_move(self) -> None:
        self.app.call_from_thread(self._set_status, "Stockfish is thinking...")
        try:
            board_copy = self.game.board.copy()
            eval_score, is_mate, mate_in = self.engine.get_evaluation(board_copy)
            self.app.call_from_thread(
                self._update_eval, eval_score, is_mate, mate_in
            )

            if board_copy.is_game_over():
                over_msg = self.game.get_game_over_message()
                if over_msg:
                    self.app.call_from_thread(self._show_game_over, over_msg)
                    return

            board_copy = self.game.board.copy()
            engine_move = self.engine.get_move(board_copy)

            board_copy.push(engine_move)
            eval_score, is_mate, mate_in = self.engine.get_evaluation(board_copy)

            self.app.call_from_thread(self._apply_engine_move, engine_move)
            self.app.call_from_thread(
                self._update_eval, eval_score, is_mate, mate_in
            )
            self.app.call_from_thread(self._set_status, "")
            self.app.call_from_thread(self._enable_input)

        except chess.engine.EngineTerminatedError:
            self.app.call_from_thread(self._set_status, "Engine crashed!")
            self.app.call_from_thread(self._enable_input)
            self.engine = None

    def _apply_engine_move(self, move: chess.Move) -> None:
        engine_san = self.game.board.san(move)
        self.game.make_engine_move(move)
        self._check_engine_book_move(engine_san)

        # If still in book, hint the next move
        if self._in_book and self._current_node is not None:
            self._set_book_feedback(
                f"Book move: play {self._current_node.move}", "dim"
            )

        self._update_display()
        over_msg = self.game.get_game_over_message()
        if over_msg:
            self._show_game_over(over_msg)

    # ------------------------------------------------------------------
    # Engine-first move (player is Black)
    # ------------------------------------------------------------------

    @work(thread=True, exclusive=True, group="engine")
    def _request_engine_move(self) -> None:
        self.app.call_from_thread(self._set_status, "Stockfish is thinking...")
        self.app.call_from_thread(self._disable_input)
        try:
            board_copy = self.game.board.copy()
            move = self.engine.get_move(board_copy)

            board_copy.push(move)
            eval_score, is_mate, mate_in = self.engine.get_evaluation(board_copy)

            self.app.call_from_thread(self._apply_engine_move, move)
            self.app.call_from_thread(
                self._update_eval, eval_score, is_mate, mate_in
            )
            self.app.call_from_thread(self._set_status, "")
            self.app.call_from_thread(self._enable_input)
        except chess.engine.EngineTerminatedError:
            self.app.call_from_thread(self._set_status, "Engine crashed!")
            self.app.call_from_thread(self._enable_input)
            self.engine = None

    # ------------------------------------------------------------------
    # Command handlers
    # ------------------------------------------------------------------

    def _handle_undo(self) -> None:
        count = self.game.undo_move()
        if count > 0:
            # Reset book tracking (simplified: go out of book on undo)
            self._in_book = False
            self._current_node = None
            self._set_book_feedback("Book tracking reset after undo", "dim")
            self._update_display()
            if self.engine:
                self._refresh_eval()
        else:
            self._set_status("No moves to undo.")

    @work(thread=True, exclusive=True, group="engine")
    def _refresh_eval(self) -> None:
        try:
            board_copy = self.game.board.copy()
            eval_score, is_mate, mate_in = self.engine.get_evaluation(board_copy)
            self.app.call_from_thread(
                self._update_eval, eval_score, is_mate, mate_in
            )
        except chess.engine.EngineTerminatedError:
            self.engine = None

    def _handle_resign(self) -> None:
        if self.player_color == chess.WHITE:
            self.game.result = "0-1"
        else:
            self.game.result = "1-0"
        self._show_game_over("You resigned. Stockfish wins.")

    def _handle_draw(self) -> None:
        if self.game.board.can_claim_draw():
            self.game.result = "1/2-1/2"
            self._show_game_over("Draw claimed.")
        else:
            self._set_status("Draw cannot be claimed in this position.")

    def _handle_save(self, cmd: list[str]) -> None:
        from datetime import datetime

        filename = (
            cmd[1]
            if len(cmd) > 1
            else f"game_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pgn"
        )
        try:
            self.game.save_pgn(filename)
            self.notify(f"Saved to {filename}", title="Game saved")
        except OSError as e:
            self._set_status(f"Error saving: {e}")

    def _quit_game(self) -> None:
        self.workers.cancel_group(self, "engine")
        self.app.pop_screen()

    # ------------------------------------------------------------------
    # Game over
    # ------------------------------------------------------------------

    def _show_game_over(self, msg: str) -> None:
        self._set_status(msg)
        self.notify(msg, title="Game Over", timeout=30)
        self._enable_input()
        inp = self.query_one("#move-input", Input)
        inp.placeholder = "Type 'save' to save or 'quit' to exit..."
        self.query_one("#btn-undo", Button).disabled = True

    # ------------------------------------------------------------------
    # Promotion helpers
    # ------------------------------------------------------------------

    def _is_promotion_attempt(self, san: str) -> bool:
        for move in self.game.board.legal_moves:
            if move.promotion:
                move_san = self.game.board.san(move)
                base = move_san.split("=")[0]
                if san == base:
                    return True
        return False

    def _prompt_promotion(self, base_san: str) -> None:
        from chess_cli.tui.widgets.promotion import PromotionModal

        def on_promotion(piece: str) -> None:
            try:
                full_san = base_san + "=" + piece
                move = self.game.board.parse_san(full_san)
                played_san = self.game.board.san(move)
                self.game.make_move(full_san)
                self._check_player_book_move(played_san)
                self._set_status("")
                self._update_display()
                over_msg = self.game.get_game_over_message()
                if over_msg:
                    self._show_game_over(over_msg)
                    return
                self._disable_input()
                self._after_player_move()
            except (
                chess.InvalidMoveError,
                chess.IllegalMoveError,
                chess.AmbiguousMoveError,
            ):
                self._set_status("Invalid promotion move.")

        self.app.push_screen(PromotionModal(), callback=on_promotion)
