"""Drill screen — predict-output challenges with hint/skip/next flow."""

import json

from rich.syntax import Syntax
from textual import work
from textual.app import ComposeResult
from textual.binding import Binding
from textual.containers import Vertical
from textual.widgets import Input, Static

from skilark_cli.answer_checker import check_answer
from skilark_cli.client import SkilarkClient


# Language mapping for Pygments syntax highlighting.
_LANG_MAP = {
    "python": "python", "go": "go", "java": "java",
    "javascript": "javascript", "typescript": "typescript",
    "rust": "rust", "c": "c", "cpp": "cpp", "sql": "sql",
    "spark": "python", "pytorch": "python", "spring-boot": "java",
    "kubernetes": "yaml", "docker": "dockerfile", "terraform": "hcl",
    "cicd": "yaml", "bash": "bash", "linux": "bash",
    "redis": "bash", "airflow": "python",
}


class DrillPane(Vertical, can_focus=True):
    """Daily drill — predict-output challenges."""

    BINDINGS = [
        Binding("h", "show_hint", "Hint", show=True),
        Binding("s", "skip", "Skip", show=True),
        Binding("n", "next_challenge", "Next", show=True),
    ]

    def __init__(
        self,
        client: SkilarkClient | None = None,
        topics: list[str] | None = None,
    ) -> None:
        super().__init__()
        self.client = client
        self.topics = topics or []
        self._challenge: dict | None = None
        self._hints: list[str] = []
        self._hint_idx: int = 0
        self._day: int = 1
        self._answered: bool = False
        self._loaded: bool = False
        self._pending_answer: str = ""

    def compose(self) -> ComposeResult:
        yield Static("Loading...", id="drill-loading")
        yield Static("No more challenges available for your topics.", id="drill-empty")
        yield Static("", id="drill-header")
        yield Static("", id="drill-question")
        yield Static("", id="drill-code")
        yield Static("", id="drill-choices")
        yield Static("[dim]Tab: answer | h: hint | s: skip[/dim]", id="drill-status")
        yield Input(placeholder="Your answer (Tab to focus, Esc for shortcuts)", id="drill-answer")
        yield Static("", id="drill-hint")
        yield Static("", id="drill-result")
        yield Static("Were you actually right? [y/n]", id="drill-self-correct")

    def on_mount(self) -> None:
        self.query_one("#drill-empty").display = False
        self.query_one("#drill-header").display = False
        self.query_one("#drill-question").display = False
        self.query_one("#drill-code").display = False
        self.query_one("#drill-choices").display = False
        self.query_one("#drill-status").display = False
        self.query_one("#drill-answer").display = False
        self.query_one("#drill-hint").display = False
        self.query_one("#drill-result").display = False
        self.query_one("#drill-self-correct").display = False
        self._fetch_challenge()

    @work(thread=True, exit_on_error=False)
    def _fetch_challenge(self) -> None:
        self.app.call_from_thread(self._set_loading, True)
        try:
            if self.client is None:
                self.app.call_from_thread(self._display_challenge, None)
                return

            # Get day count from stats on first load
            if not self._loaded:
                try:
                    stats = self.client.get_stats()
                    self._day = stats.get("total_completed", 0) + 1
                except Exception:
                    self._day = 1

            challenge = self.client.get_next_challenge(self.topics)
            self.app.call_from_thread(self._display_challenge, challenge)
        except Exception:
            self.app.call_from_thread(self._display_challenge, None)

    def _set_loading(self, show: bool) -> None:
        if self.is_attached:
            self.query_one("#drill-loading").display = show

    def _display_challenge(self, challenge: dict | None) -> None:
        if not self.is_attached:
            return
        self._loaded = True
        self._set_loading(False)
        self._answered = False
        self._hint_idx = 0

        # Hide result/self-correct from previous challenge
        self.query_one("#drill-result").display = False
        self.query_one("#drill-self-correct").display = False
        self.query_one("#drill-hint", Static).update("")
        self.query_one("#drill-hint").display = False

        if challenge is None:
            self._challenge = None
            self.query_one("#drill-empty").display = True
            self.query_one("#drill-header").display = False
            self.query_one("#drill-question").display = False
            self.query_one("#drill-code").display = False
            self.query_one("#drill-choices").display = False
            self.query_one("#drill-status").display = False
            self.query_one("#drill-answer").display = False
            return

        self._challenge = challenge
        self.query_one("#drill-empty").display = False

        # Parse hints
        hints = challenge.get("hints") or []
        if isinstance(hints, str):
            try:
                hints = json.loads(hints)
            except (json.JSONDecodeError, TypeError):
                hints = []
        self._hints = hints

        # Header: Day N . source . title
        source = challenge.get("source", "")
        title = challenge.get("title", "")
        header = self.query_one("#drill-header", Static)
        header.update(f"Day {self._day} \u00b7 {source} \u00b7 {title}")
        header.display = True

        # Question
        question = challenge.get("question", "")
        q_widget = self.query_one("#drill-question", Static)
        q_widget.update(question)
        q_widget.display = bool(question)

        # Code (Rich Syntax for proper highlighting)
        code = challenge.get("code_display") or ""
        code_widget = self.query_one("#drill-code", Static)
        if code.strip():
            lang = _LANG_MAP.get(source.lower(), "text")
            code_widget.update(
                Syntax(code, lang, theme="monokai", line_numbers=False)
            )
            code_widget.display = True
        else:
            code_widget.display = False

        # Multiple choice
        challenge_type = challenge.get("challenge_type", "predict_output")
        choices = challenge.get("choices") or []
        if isinstance(choices, str):
            try:
                choices = json.loads(choices)
            except (json.JSONDecodeError, TypeError):
                choices = []

        choices_widget = self.query_one("#drill-choices", Static)
        if challenge_type == "multiple_choice" and choices:
            choices_widget.update("\n".join(choices))
            choices_widget.display = True
        else:
            choices_widget.display = False

        # Status hint and answer input
        self.query_one("#drill-status").display = True
        answer_input = self.query_one("#drill-answer", Input)
        answer_input.value = ""
        answer_input.display = True

    def on_input_submitted(self, event: Input.Submitted) -> None:
        if event.input.id == "drill-answer" and not self._answered:
            self._check_answer(event.value.strip())

    def _check_answer(self, user_answer: str) -> None:
        if not user_answer or not self._challenge:
            return

        expected = self._challenge.get("expected_answer", "")
        correct = check_answer(user_answer, expected)
        self._answered = True

        # Hide answer input and status, move focus to pane for n/y/n keys
        self.query_one("#drill-answer").display = False
        self.query_one("#drill-status").display = False
        self.screen.set_focus(self)

        explanation = self._challenge.get("explanation", "")
        deep_link = self._challenge.get("deep_link", "")

        result = self.query_one("#drill-result", Static)

        if correct:
            lines = ["Correct!", "", explanation]
            if deep_link:
                lines.append(f"\n-> {deep_link}")
            result.update("\n".join(lines))
            result.display = True
            self._record_completion(user_answer, correct=True, self_corrected=False)
        else:
            lines = [f"Not quite. The answer is: {expected}", "", explanation]
            if deep_link:
                lines.append(f"\n-> {deep_link}")
            result.update("\n".join(lines))
            result.display = True
            # Wait for self-correct y/n before recording
            self.query_one("#drill-self-correct").display = True
            self._pending_answer = user_answer

    def on_key(self, event) -> None:
        # Escape to unfocus input (so h/s/n shortcuts work)
        if event.key == "escape":
            answer_input = self.query_one("#drill-answer", Input)
            if answer_input.has_focus:
                self.screen.set_focus(self)
                event.prevent_default()
                event.stop()
                return

        # Handle self-correct y/n
        self_correct = self.query_one("#drill-self-correct")
        if self_correct.display and event.key in ("y", "n"):
            self_corrected = event.key == "y"
            correct = self_corrected
            self_correct.display = False
            self._record_completion(
                self._pending_answer, correct=correct, self_corrected=self_corrected
            )
            event.prevent_default()
            event.stop()

    def _record_completion(
        self, answer: str, *, correct: bool, self_corrected: bool
    ) -> None:
        if self._challenge:
            self._submit_completion(
                self._challenge["id"], answer, correct, self_corrected
            )

    @work(thread=True, exit_on_error=False)
    def _submit_completion(
        self, challenge_id: str, answer: str, correct: bool, self_corrected: bool
    ) -> None:
        try:
            if self.client:
                self.client.complete_challenge(
                    challenge_id,
                    answer=answer,
                    correct=correct,
                    self_corrected=self_corrected,
                )
        except Exception:
            pass  # Best-effort recording

    def action_show_hint(self) -> None:
        if self._answered:
            return
        hint_widget = self.query_one("#drill-hint", Static)
        if not self._hints:
            hint_widget.update("No hints available for this challenge.")
        elif self._hint_idx < len(self._hints):
            hint_widget.update(self._hints[self._hint_idx])
            self._hint_idx += 1
        else:
            hint_widget.update("No more hints.")
        hint_widget.display = True

    def action_skip(self) -> None:
        if self._answered:
            return
        self._fetch_challenge()

    def action_next_challenge(self) -> None:
        if not self._answered:
            return
        self._day += 1
        self._fetch_challenge()
