"""Recall screen for the ALiCE TUI — session history browser with DataTable."""

from __future__ import annotations

import json
from typing import TYPE_CHECKING

from textual.binding import Binding
from textual.containers import Horizontal, Vertical
from textual.widgets import Button, DataTable, Footer, Input, Label, Select

from alice_cli.commands.recall import _VALID_TYPES, filter_entries
from alice_cli.tui.screens.base import AliceScreen
from alice_cli.tui.widgets.breadcrumb import BreadcrumbBar
from alice_cli.tui.widgets.header_bar import AliceHeaderBar

if TYPE_CHECKING:
    from textual.app import ComposeResult

    from alice_cli.models import IndexEntry, SessionRecord

# Categories to try when loading a full session record
_SESSION_CATEGORIES = ("chat", "invoke", "dialog", "batch", "compare")


class RecallScreen(AliceScreen):
    """Session history browser with DataTable."""

    BINDINGS = [
        Binding("escape", "pop_screen", "Back"),
    ]

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

    RecallScreen #filter-row {
        height: 3;
        padding: 0 1;
        align: left middle;
    }

    RecallScreen #since-input {
        width: 16;
    }

    RecallScreen #until-input {
        width: 16;
    }

    RecallScreen #type-select {
        width: 16;
    }

    RecallScreen #model-input {
        width: 16;
    }

    RecallScreen #keyword-input {
        width: 1fr;
    }

    RecallScreen #search-btn {
        min-width: 10;
    }

    RecallScreen #recall-table {
        height: 1fr;
    }

    RecallScreen #detail-panel {
        height: auto;
        max-height: 40%;
        border: tall #1A3A5C;
        background: #0A1628;
        padding: 1 2;
        display: none;
    }

    RecallScreen #detail-content {
        height: auto;
        color: #FFB000;
    }

    RecallScreen #detail-actions {
        height: 3;
        padding: 0 1;
        align: left middle;
    }

    RecallScreen #resume-btn {
        min-width: 14;
    }

    RecallScreen #copy-id-btn {
        min-width: 12;
    }

    RecallScreen #close-detail-btn {
        min-width: 12;
    }

    RecallScreen #status-label {
        width: 1fr;
        color: #805800;
        padding: 0 2;
    }
    """

    def __init__(self) -> None:
        super().__init__()
        self._entries: list[IndexEntry] = []
        self._selected_record: SessionRecord | None = None
        self._selected_entry: IndexEntry | None = None
        self._loading: bool = False

    def compose(self) -> ComposeResult:
        yield AliceHeaderBar()
        yield BreadcrumbBar(id="breadcrumb")

        # Filter row
        with Horizontal(id="filter-row"):
            yield Input(placeholder="Since (YYYY-MM-DD)", id="since-input")
            yield Input(placeholder="Until (YYYY-MM-DD)", id="until-input")
            yield Select(
                [(t, t) for t in _VALID_TYPES],
                prompt="All types",
                allow_blank=True,
                id="type-select",
            )
            yield Input(placeholder="Model", id="model-input")
            yield Input(placeholder="Keyword", id="keyword-input")
            yield Button("Search", id="search-btn", variant="primary")

        # DataTable
        table: DataTable[str] = DataTable(id="recall-table")
        table.add_columns("Timestamp", "Type", "Model", "Prompt Preview")
        yield table

        # Detail panel (hidden by default)
        with Vertical(id="detail-panel"):
            yield Label("", id="detail-content")
            with Horizontal(id="detail-actions"):
                yield Button("Resume Chat", id="resume-btn", disabled=True)
                yield Button("Copy ID", id="copy-id-btn", disabled=True)
                yield Button("Close Detail", id="close-detail-btn")

        yield Label("", id="status-label")
        yield Footer()

    def key_j(self) -> None:
        """Move DataTable cursor down or focus next widget (vim-style down).

        When a DataTable has focus the cursor row advances; otherwise
        focus moves to the next focusable child.  No-ops when a text
        input (Input / TextArea) is focused so ``j`` types normally.
        """
        if self._text_input_has_focus():
            return
        focused = self.focused
        if isinstance(focused, DataTable):
            focused.action_cursor_down()
        else:
            self.focus_next()

    def key_k(self) -> None:
        """Move DataTable cursor up or focus previous widget (vim-style up).

        When a DataTable has focus the cursor row retreats; otherwise
        focus moves to the previous focusable child.  No-ops when a text
        input (Input / TextArea) is focused so ``k`` types normally.
        """
        if self._text_input_has_focus():
            return
        focused = self.focused
        if isinstance(focused, DataTable):
            focused.action_cursor_up()
        else:
            self.focus_previous()

    def on_mount(self) -> None:
        """Load the session index on mount."""
        self._load_index()

    def on_button_pressed(self, event: Button.Pressed) -> None:
        """Handle button presses."""
        if event.button.id == "search-btn":
            self._apply_filters()
        elif event.button.id == "close-detail-btn":
            self._close_detail()
        elif event.button.id == "resume-btn":
            self._resume_chat()
        elif event.button.id == "copy-id-btn":
            self._copy_session_id()

    def on_data_table_row_selected(self, event: DataTable.RowSelected) -> None:
        """Show detail panel when a row is selected."""
        row_key = event.row_key
        table = self.query_one("#recall-table", DataTable)
        row_index = table.get_row_index(row_key)
        if 0 <= row_index < len(self._entries):
            entry = self._entries[row_index]
            self._selected_entry = entry
            self._show_detail(entry)

    def _load_index(self) -> None:
        """Load the session index in a worker thread."""
        if self._loading:
            return
        self._loading = True
        self.query_one("#status-label", Label).update("Loading session index…")
        self.run_worker(self._fetch_index, thread=True)

    def _fetch_index(self) -> None:
        """Worker thread: fetch index from CloudLocker."""
        try:
            from alice_cli.auth import detect_username
            from alice_cli.constants import get_memory_bucket
            from alice_cli.locker import CloudLocker

            result = detect_username(self.config.profile, self.config.region)
            locker = CloudLocker(
                config=self.config,
                username=result.username,
                bucket=get_memory_bucket(self.config.namespace, self.config.environment),
            )
            index = locker.load_index()
            entries = filter_entries(index.entries, limit=50)
            self._entries = entries
            self.app.call_from_thread(self._populate_table, entries)
        except Exception as exc:
            self.app.call_from_thread(
                self.query_one("#status-label", Label).update,
                f"[#FF6600]Error loading index: {exc}[/]",
            )
        finally:
            self._loading = False

    def _populate_table(self, entries: list[IndexEntry]) -> None:
        """Populate the DataTable with index entries."""
        table = self.query_one("#recall-table", DataTable)
        table.clear()
        for entry in entries:
            table.add_row(
                entry.timestamp[:19],
                entry.type,
                entry.model,
                entry.prompt_preview[:80],
            )
        count = len(entries)
        self.query_one("#status-label", Label).update(f"Showing {count} session(s)")

    def _apply_filters(self) -> None:
        """Apply filter inputs and repopulate the table."""
        since = self.query_one("#since-input", Input).value.strip() or None
        until = self.query_one("#until-input", Input).value.strip() or None
        model = self.query_one("#model-input", Input).value.strip() or None
        keyword = self.query_one("#keyword-input", Input).value.strip() or None

        type_select = self.query_one("#type-select", Select)
        entry_type = type_select.value if type_select.value != Select.BLANK else None

        # Re-filter from the full loaded entries
        if self._loading:
            return
        self._loading = True
        self.query_one("#status-label", Label).update("Searching…")
        self.run_worker(
            lambda: self._fetch_filtered(since, until, model, entry_type, keyword),
            thread=True,
        )

    def _fetch_filtered(
        self,
        since: str | None,
        until: str | None,
        model: str | None,
        entry_type: str | None,
        keyword: str | None,
    ) -> None:
        """Worker thread: fetch and filter index."""
        try:
            from alice_cli.auth import detect_username
            from alice_cli.constants import get_memory_bucket
            from alice_cli.locker import CloudLocker

            result = detect_username(self.config.profile, self.config.region)
            locker = CloudLocker(
                config=self.config,
                username=result.username,
                bucket=get_memory_bucket(self.config.namespace, self.config.environment),
            )
            index = locker.load_index()
            entries = filter_entries(
                index.entries,
                since=since,
                until=until,
                model=model,
                entry_type=entry_type,
                keyword=keyword,
                limit=50,
            )
            self._entries = entries
            self.app.call_from_thread(self._populate_table, entries)
        except Exception as exc:
            self.app.call_from_thread(
                self.query_one("#status-label", Label).update,
                f"[#FF6600]Error: {exc}[/]",
            )
        finally:
            self._loading = False

    def _show_detail(self, entry: IndexEntry) -> None:
        """Show the detail panel for a selected entry."""
        detail_panel = self.query_one("#detail-panel", Vertical)
        detail_panel.styles.display = "block"

        # Show basic info immediately
        detail_text = (
            f"[bold]Session:[/bold] {entry.id}\n"
            f"[bold]Type:[/bold]    {entry.type}\n"
            f"[bold]Time:[/bold]    {entry.timestamp}\n"
            f"[bold]Model:[/bold]   {entry.model}\n"
            f"[bold]Prompt:[/bold]  {entry.prompt_preview}"
        )
        self.query_one("#detail-content", Label).update(detail_text)

        # Enable buttons
        resume_btn = self.query_one("#resume-btn", Button)
        resume_btn.disabled = entry.type != "chat"
        self.query_one("#copy-id-btn", Button).disabled = False

        # Load full record in background
        self.run_worker(
            lambda: self._fetch_session_detail(entry),
            thread=True,
        )

    def _fetch_session_detail(self, entry: IndexEntry) -> None:
        """Worker thread: load the full session record."""
        try:
            from alice_cli.auth import detect_username
            from alice_cli.constants import get_memory_bucket
            from alice_cli.locker import CloudLocker

            result = detect_username(self.config.profile, self.config.region)
            locker = CloudLocker(
                config=self.config,
                username=result.username,
                bucket=get_memory_bucket(self.config.namespace, self.config.environment),
            )

            record = None
            for cat in _SESSION_CATEGORIES:
                try:
                    record = locker.load_session(entry.id, cat)
                    break
                except Exception:
                    continue

            if record is not None:
                self._selected_record = record
                self.app.call_from_thread(self._display_full_record, record)
        except Exception:
            pass  # Best-effort — basic info is already shown

    def _display_full_record(self, record: SessionRecord) -> None:
        """Update the detail panel with the full session record."""
        lines = [
            f"[bold]Session:[/bold] {record.id}",
            f"[bold]Type:[/bold]    {record.type}",
            f"[bold]Time:[/bold]    {record.timestamp}",
            f"[bold]Model:[/bold]   {record.model}",
        ]

        if record.prompt:
            lines.append(f"\n[bold]Prompt:[/bold]\n{record.prompt}")

        if record.response:
            lines.append(f"\n[bold]Response:[/bold]\n{record.response}")

        if record.tokens:
            lines.append(
                f"\n[dim]Tokens: {record.tokens.input} in / "
                f"{record.tokens.output} out / {record.tokens.total} total[/dim]"
            )

        if record.metadata:
            meta_str = json.dumps(record.metadata, indent=2, ensure_ascii=False)
            lines.append(f"\n[bold]Metadata:[/bold]\n{meta_str}")

        self.query_one("#detail-content", Label).update("\n".join(lines))

    def _close_detail(self) -> None:
        """Hide the detail panel."""
        self.query_one("#detail-panel", Vertical).styles.display = "none"
        self._selected_record = None
        self._selected_entry = None

    def _resume_chat(self) -> None:
        """Open ChatScreen with pre-loaded message history from the selected session."""
        if self._selected_record is None:
            return
        if self._selected_record.type != "chat":
            return

        metadata = self._selected_record.metadata or {}
        messages_raw = metadata.get("messages", [])

        from alice_cli.models import ChatMessage
        from alice_cli.tui.screens.chat import ChatScreen

        chat_screen = ChatScreen()
        # Pre-load the message history
        chat_screen._messages = [
            ChatMessage(role=m["role"], content=m["content"])
            for m in messages_raw
            if "role" in m and "content" in m
        ]
        self.app.push_screen(chat_screen)

    def _copy_session_id(self) -> None:
        """Copy the selected session ID to clipboard via notification."""
        if self._selected_entry is None:
            return
        session_id = self._selected_entry.id
        # Textual doesn't have clipboard access, so show as notification
        self.notify(f"Session ID: {session_id}", severity="information")
