from __future__ import annotations

import random
import shutil
from dataclasses import dataclass

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

from chess_cli.main import DIFFICULTY_CHOICES

# ── Constants ────────────────────────────────────────────────────────

COLOR_OPTIONS = ["White", "Black", "Random"]

ASCII_TITLE = """\
      ♚  C H E S S   C L I
         ━━━━━━━━━━━━━━━━━"""


@dataclass
class EngineProfile:
    name: str
    threads: int
    hash_mb: int
    move_time: float | None
    description: str


ENGINE_PROFILES = [
    EngineProfile("Quick",    1,  16,  None, "1 thread, 16MB hash — fast & light"),
    EngineProfile("Standard", 2,  64,  None, "2 threads, 64MB hash — balanced"),
    EngineProfile("Strong",   4, 256,  5.0,  "4 threads, 256MB hash, 5s/move"),
    EngineProfile("Maximum",  16, 1024, 10.0, "16 threads, 1GB hash, 10s/move"),
    EngineProfile("Custom",   0,  0,   None, "Configure manually"),
]

THREAD_OPTIONS = [1, 2, 4, 8, 12, 16]
HASH_OPTIONS = [16, 64, 256, 512, 1024]
MOVE_TIME_OPTIONS: list[float | None] = [None, 1.0, 2.0, 5.0, 10.0, 30.0]
MOVE_TIME_LABELS = ["Auto", "1s", "2s", "5s", "10s", "30s"]

# Ordered list of focusable option IDs.
_BASE_OPTIONS = ["opt-color", "opt-difficulty", "opt-engine"]
_CUSTOM_OPTIONS = ["opt-threads", "opt-hash", "opt-movetime"]
_BUTTON_OPTIONS = ["btn-start", "btn-analyze"]

# Difficulty bar width (characters).
_BAR_WIDTH = 20


# ── MenuOption widget ────────────────────────────────────────────────

class MenuOption(Static):
    """A selectable menu row with a label and a current value."""

    highlighted = reactive(False)

    DEFAULT_CSS = """
    MenuOption {
        width: 100%;
        height: 1;
        padding: 0 1;
    }
    MenuOption.highlighted {
        background: $primary-background;
    }
    MenuOption.hidden {
        display: none;
    }
    """

    def __init__(self, label: str, option_id: str) -> None:
        super().__init__(id=option_id)
        self.label = label

    def set_value(self, value_renderable: str | Text) -> None:
        if isinstance(value_renderable, str):
            text = Text()
            text.append(f" {self.label:<14} ", style="bold" if self.highlighted else "")
            text.append(value_renderable)
        else:
            text = Text()
            text.append(f" {self.label:<14} ", style="bold" if self.highlighted else "")
            text.append_text(value_renderable)
        self.update(text)

    def watch_highlighted(self, value: bool) -> None:
        self.set_class(value, "highlighted")


# ── MenuScreen ───────────────────────────────────────────────────────

class MenuScreen(Screen):

    DEFAULT_CSS = """
MenuScreen {
    align: center middle;
}

#menu-container {
    width: 56;
    height: auto;
    border: heavy $primary;
    padding: 1 2;
    background: $surface;
}

#menu-title {
    text-align: center;
    width: 100%;
    margin-bottom: 1;
    color: $text;
}

.section-header {
    width: 100%;
    height: 1;
    color: $text-muted;
    margin: 1 0 0 0;
    padding: 0 1;
}

#engine-description {
    width: 100%;
    height: 1;
    color: $text-muted;
    padding: 0 1 0 17;
}

#btn-start {
    width: 100%;
    margin-top: 1;
}

#btn-analyze {
    width: 100%;
}

#status-bar {
    width: 100%;
    height: 1;
    margin-top: 1;
    color: $text-muted;
    padding: 0 1;
}
"""

    BINDINGS = [
        ("up", "focus_prev", "Up"),
        ("down", "focus_next", "Down"),
        ("left", "cycle_left", "◀"),
        ("right", "cycle_right", "▶"),
        ("enter", "select", "Select"),
        # Legacy letter shortcuts
        ("c", "shortcut_color_fwd", "Color"),
        ("C", "shortcut_color_back", ""),
        ("d", "shortcut_diff_fwd", "Difficulty"),
        ("D", "shortcut_diff_back", ""),
        ("e", "shortcut_engine_fwd", "Engine"),
        ("E", "shortcut_engine_back", ""),
        ("t", "shortcut_threads_fwd", ""),
        ("T", "shortcut_threads_back", ""),
        ("h", "shortcut_hash_fwd", ""),
        ("H", "shortcut_hash_back", ""),
        ("m", "shortcut_movetime_fwd", ""),
        ("M", "shortcut_movetime_back", ""),
        ("a", "analyze", "Analyze"),
        ("q", "quit", "Quit"),
    ]

    color_index: reactive[int] = reactive(0)
    difficulty_index: reactive[int] = reactive(5)
    engine_index: reactive[int] = reactive(0)
    threads_index: reactive[int] = reactive(0)
    hash_index: reactive[int] = reactive(0)
    movetime_index: reactive[int] = reactive(0)
    focus_index: reactive[int] = reactive(0)

    def compose(self) -> ComposeResult:
        yield Header(show_clock=False)
        with Vertical(id="menu-container"):
            yield Static(ASCII_TITLE, id="menu-title")
            yield Static("── Game Setup ─────────────────────────", classes="section-header")
            yield MenuOption("Color", "opt-color")
            yield MenuOption("Difficulty", "opt-difficulty")
            yield Static("── Engine ─────────────────────────────", classes="section-header")
            yield MenuOption("Profile", "opt-engine")
            yield Static("", id="engine-description")
            yield MenuOption("Threads", "opt-threads")
            yield MenuOption("Hash", "opt-hash")
            yield MenuOption("Move time", "opt-movetime")
            yield Static("────────────────────────────────────────", classes="section-header")
            yield Button("♚ Start Game", id="btn-start", variant="primary")
            yield Button("♛ Analyze PGN", id="btn-analyze")
            yield Static("", id="status-bar")
        yield Footer()

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

    def on_mount(self) -> None:
        # Hide custom sub-options initially
        self._update_custom_visibility()
        # Render all option values
        self._render_color()
        self._render_difficulty()
        self._render_engine()
        self._render_custom_options()
        self._update_focus_highlight()
        self._render_status_bar()

    # ------------------------------------------------------------------
    # Focus navigation
    # ------------------------------------------------------------------

    @property
    def _focusable_ids(self) -> list[str]:
        ids = list(_BASE_OPTIONS)
        if self._is_custom:
            ids.extend(_CUSTOM_OPTIONS)
        ids.extend(_BUTTON_OPTIONS)
        return ids

    @property
    def _is_custom(self) -> bool:
        return ENGINE_PROFILES[self.engine_index].name == "Custom"

    def _update_focus_highlight(self) -> None:
        focusable = self._focusable_ids
        for opt_id in _BASE_OPTIONS + _CUSTOM_OPTIONS:
            try:
                widget = self.query_one(f"#{opt_id}", MenuOption)
                widget.highlighted = opt_id in focusable and focusable.index(opt_id) == self.focus_index
            except Exception:
                pass
        # Buttons: use Textual's built-in focus
        for btn_id in _BUTTON_OPTIONS:
            try:
                btn = self.query_one(f"#{btn_id}", Button)
                idx = focusable.index(btn_id) if btn_id in focusable else -1
                if idx == self.focus_index:
                    btn.focus()
                    btn.add_class("highlighted")
                else:
                    btn.remove_class("highlighted")
            except Exception:
                pass

    def watch_focus_index(self, value: int) -> None:
        self._update_focus_highlight()

    def action_focus_prev(self) -> None:
        total = len(self._focusable_ids)
        self.focus_index = (self.focus_index - 1) % total

    def action_focus_next(self) -> None:
        total = len(self._focusable_ids)
        self.focus_index = (self.focus_index + 1) % total

    def action_cycle_left(self) -> None:
        self._cycle_focused(-1)

    def action_cycle_right(self) -> None:
        self._cycle_focused(1)

    def _cycle_focused(self, direction: int) -> None:
        focusable = self._focusable_ids
        if self.focus_index >= len(focusable):
            return
        current_id = focusable[self.focus_index]
        if current_id == "opt-color":
            self.color_index = (self.color_index + direction) % len(COLOR_OPTIONS)
        elif current_id == "opt-difficulty":
            self.difficulty_index = (self.difficulty_index + direction) % len(DIFFICULTY_CHOICES)
        elif current_id == "opt-engine":
            self.engine_index = (self.engine_index + direction) % len(ENGINE_PROFILES)
        elif current_id == "opt-threads":
            self.threads_index = (self.threads_index + direction) % len(THREAD_OPTIONS)
        elif current_id == "opt-hash":
            self.hash_index = (self.hash_index + direction) % len(HASH_OPTIONS)
        elif current_id == "opt-movetime":
            self.movetime_index = (self.movetime_index + direction) % len(MOVE_TIME_OPTIONS)

    def action_select(self) -> None:
        focusable = self._focusable_ids
        if self.focus_index >= len(focusable):
            return
        current_id = focusable[self.focus_index]
        if current_id == "btn-start":
            self._start_game()
        elif current_id == "btn-analyze":
            self.action_analyze()
        else:
            # On option rows, Enter cycles forward (same as right arrow)
            self._cycle_focused(1)

    # ------------------------------------------------------------------
    # Legacy letter shortcuts
    # ------------------------------------------------------------------

    def action_shortcut_color_fwd(self) -> None:
        self.color_index = (self.color_index + 1) % len(COLOR_OPTIONS)

    def action_shortcut_color_back(self) -> None:
        self.color_index = (self.color_index - 1) % len(COLOR_OPTIONS)

    def action_shortcut_diff_fwd(self) -> None:
        self.difficulty_index = (self.difficulty_index + 1) % len(DIFFICULTY_CHOICES)

    def action_shortcut_diff_back(self) -> None:
        self.difficulty_index = (self.difficulty_index - 1) % len(DIFFICULTY_CHOICES)

    def action_shortcut_engine_fwd(self) -> None:
        self.engine_index = (self.engine_index + 1) % len(ENGINE_PROFILES)

    def action_shortcut_engine_back(self) -> None:
        self.engine_index = (self.engine_index - 1) % len(ENGINE_PROFILES)

    def action_shortcut_threads_fwd(self) -> None:
        if self._is_custom:
            self.threads_index = (self.threads_index + 1) % len(THREAD_OPTIONS)

    def action_shortcut_threads_back(self) -> None:
        if self._is_custom:
            self.threads_index = (self.threads_index - 1) % len(THREAD_OPTIONS)

    def action_shortcut_hash_fwd(self) -> None:
        if self._is_custom:
            self.hash_index = (self.hash_index + 1) % len(HASH_OPTIONS)

    def action_shortcut_hash_back(self) -> None:
        if self._is_custom:
            self.hash_index = (self.hash_index - 1) % len(HASH_OPTIONS)

    def action_shortcut_movetime_fwd(self) -> None:
        if self._is_custom:
            self.movetime_index = (self.movetime_index + 1) % len(MOVE_TIME_OPTIONS)

    def action_shortcut_movetime_back(self) -> None:
        if self._is_custom:
            self.movetime_index = (self.movetime_index - 1) % len(MOVE_TIME_OPTIONS)

    # ------------------------------------------------------------------
    # Rendering helpers
    # ------------------------------------------------------------------

    def _render_color(self) -> None:
        text = Text()
        for i, option in enumerate(COLOR_OPTIONS):
            if i > 0:
                text.append("   ")
            marker = "\u25c9" if i == self.color_index else "\u25cb"
            style = "bold" if i == self.color_index else "dim"
            text.append(f"{marker} {option}", style=style)
        try:
            self.query_one("#opt-color", MenuOption).set_value(text)
        except Exception:
            pass

    def _render_difficulty(self) -> None:
        from chess_cli.engine import DIFFICULTY_PRESETS

        idx = self.difficulty_index
        name = DIFFICULTY_CHOICES[idx]
        elo = DIFFICULTY_PRESETS[name]
        total = len(DIFFICULTY_CHOICES)

        # Build gradient bar
        filled = int((idx + 1) / total * _BAR_WIDTH)
        text = Text()
        for i in range(_BAR_WIDTH):
            if i < filled:
                # Green -> Yellow -> Red gradient
                frac = i / _BAR_WIDTH
                if frac < 0.5:
                    r = int(100 + 155 * (frac * 2))
                    g = 200
                else:
                    r = 255
                    g = int(200 * (1 - (frac - 0.5) * 2))
                text.append("█", style=f"rgb({r},{g},50)")
            else:
                text.append("░", style="dim")

        text.append(f" {name.capitalize()}")
        if elo is None:
            text.append(" (max)", style="dim")
        else:
            text.append(f" ({elo})", style="dim")

        try:
            self.query_one("#opt-difficulty", MenuOption).set_value(text)
        except Exception:
            pass

    def _render_engine(self) -> None:
        profile = ENGINE_PROFILES[self.engine_index]
        text = Text()
        text.append(f"◀ {profile.name} ▶")

        try:
            self.query_one("#opt-engine", MenuOption).set_value(text)
        except Exception:
            pass

        # Description line
        if not self._is_custom:
            desc = f"{profile.description}"
        else:
            desc = "Configure threads, hash, and move time below"
        try:
            self.query_one("#engine-description", Static).update(desc)
        except Exception:
            pass

    def _render_custom_options(self) -> None:
        if not self._is_custom:
            return
        try:
            self.query_one("#opt-threads", MenuOption).set_value(
                f"◀ {THREAD_OPTIONS[self.threads_index]} ▶"
            )
        except Exception:
            pass
        try:
            self.query_one("#opt-hash", MenuOption).set_value(
                f"◀ {HASH_OPTIONS[self.hash_index]} MB ▶"
            )
        except Exception:
            pass
        try:
            self.query_one("#opt-movetime", MenuOption).set_value(
                f"◀ {MOVE_TIME_LABELS[self.movetime_index]} ▶"
            )
        except Exception:
            pass

    def _update_custom_visibility(self) -> None:
        is_custom = self._is_custom
        for opt_id in _CUSTOM_OPTIONS:
            try:
                widget = self.query_one(f"#{opt_id}", MenuOption)
                widget.set_class(not is_custom, "hidden")
            except Exception:
                pass
        # Clamp focus if custom options disappeared
        total = len(self._focusable_ids)
        if self.focus_index >= total:
            self.focus_index = total - 1

    def _render_status_bar(self) -> None:
        sf_found = shutil.which("stockfish") is not None
        sf_status = "✓ detected" if sf_found else "✗ not found"
        sf_style = "green" if sf_found else "red"
        text = Text()
        text.append("v0.1.0", style="dim")
        text.append("                    ", style="dim")
        text.append("Stockfish: ", style="dim")
        text.append(sf_status, style=sf_style)
        try:
            self.query_one("#status-bar", Static).update(text)
        except Exception:
            pass

    # ------------------------------------------------------------------
    # Watchers
    # ------------------------------------------------------------------

    def watch_color_index(self, value: int) -> None:
        self._render_color()

    def watch_difficulty_index(self, value: int) -> None:
        self._render_difficulty()

    def watch_engine_index(self, value: int) -> None:
        self._render_engine()
        self._update_custom_visibility()
        self._render_custom_options()
        self._update_focus_highlight()

    def watch_threads_index(self, value: int) -> None:
        self._render_custom_options()

    def watch_hash_index(self, value: int) -> None:
        self._render_custom_options()

    def watch_movetime_index(self, value: int) -> None:
        self._render_custom_options()

    # ------------------------------------------------------------------
    # Actions
    # ------------------------------------------------------------------

    def _get_engine_settings(self) -> tuple[int, int, float | None]:
        profile = ENGINE_PROFILES[self.engine_index]
        if self._is_custom:
            return (
                THREAD_OPTIONS[self.threads_index],
                HASH_OPTIONS[self.hash_index],
                MOVE_TIME_OPTIONS[self.movetime_index],
            )
        return profile.threads, profile.hash_mb, profile.move_time

    def _start_game(self) -> None:
        if self.color_index == 0:
            color = chess.WHITE
        elif self.color_index == 1:
            color = chess.BLACK
        else:
            color = random.choice([chess.WHITE, chess.BLACK])

        difficulty = DIFFICULTY_CHOICES[self.difficulty_index]
        threads, hash_mb, move_time = self._get_engine_settings()

        from chess_cli.tui.screens.game import GameScreen

        self.app.push_screen(GameScreen(
            color=color,
            difficulty=difficulty,
            threads=threads,
            hash_mb=hash_mb,
            move_time=move_time,
        ))

    def on_button_pressed(self, event: Button.Pressed) -> None:
        if event.button.id == "btn-start":
            self._start_game()
        elif event.button.id == "btn-analyze":
            self.action_analyze()

    def action_analyze(self) -> None:
        self.notify("Analysis from file not yet implemented")

    def action_quit(self) -> None:
        self.app.exit()
