"""Terminal chat client for local development."""

from __future__ import annotations

import contextlib
import getpass
import logging
import os
import secrets
from collections.abc import AsyncGenerator, Generator
from contextlib import contextmanager
from dataclasses import dataclass, field
from datetime import UTC, datetime
from typing import Any

from emoji import emojize
from prompt_toolkit import PromptSession
from prompt_toolkit.history import FileHistory
from prompt_toolkit.patch_stdout import patch_stdout
from rich import box
from rich.console import Console
from rich.markdown import Markdown
from rich.panel import Panel
from rich.text import Text

from docketeer.chat import (
    ChatClient,
    IncomingMessage,
    OnHistoryCallback,
    RoomInfo,
    RoomKind,
    RoomMessage,
)

log = logging.getLogger(__name__)

ROOM_ID = "terminal"


@dataclass
class _TUIStream:
    """Tracks an in-progress streamed reply."""

    parts: list[str] = field(default_factory=list)
    lines_printed: int = 0
    extra_lines: int = 0


@contextmanager
def _patched_stdout() -> Generator[None]:
    """Wrap patch_stdout so tests can easily mock it out."""
    with patch_stdout(raw=True):
        yield


class TUIClient(ChatClient):
    """Chat client that reads from stdin and writes to the terminal.

    Uses prompt_toolkit to maintain a fixed input area at the bottom of the
    terminal while agent responses scroll above it.
    """

    username = "docketeer"
    user_id = "docketeer-tui"

    def __init__(self) -> None:
        self._console = Console()
        self._session: PromptSession[str] | None = None
        self._messages: list[RoomMessage] = []
        self._closed = False
        self._stdout_ctx: contextlib.ExitStack | None = None
        self._human_username = os.environ.get(
            "DOCKETEER_TUI_USERNAME", getpass.getuser()
        )
        self._human_user_id = f"tui-{self._human_username}"
        self._reaction_emojis: list[str] = []
        self._reactions_printed = False
        self._active_stream: _TUIStream | None = None

    async def __aenter__(self) -> TUIClient:
        from docketeer import environment

        log_path = environment.DATA_DIR / "docketeer.log"

        history_file = environment.DATA_DIR / ".tui-history.txt"
        self._session = PromptSession(history=FileHistory(str(history_file)))

        # patch_stdout makes all print() / Console.print() output render
        # above the prompt_toolkit input line — the key to two-region UX
        self._stdout_ctx = contextlib.ExitStack()
        self._stdout_ctx.enter_context(_patched_stdout())

        # recreate Console AFTER patch_stdout so it writes through the proxy;
        # force_terminal=True because the proxy doesn't report as a TTY
        self._console = Console(force_terminal=True)

        self._console.print()
        self._console.rule("[bold]docketeer[/bold]")
        self._console.print(f"  logged in as [bold]{self._human_username}[/bold]")
        self._console.print("  type a message and press enter. ctrl-c to quit.")
        self._console.print(f"  logs: {log_path}")
        self._console.print()
        return self

    async def __aexit__(self, *exc: object) -> None:
        self._closed = True
        self._console.print()
        self._console.rule("[dim]disconnected[/dim]")
        if self._stdout_ctx:
            self._stdout_ctx.close()
            self._stdout_ctx = None

    async def incoming_messages(
        self,
        on_history: OnHistoryCallback | None = None,
    ) -> AsyncGenerator[IncomingMessage, None]:
        while not self._closed:
            try:
                text = await self._read_input()
            except (EOFError, KeyboardInterrupt):
                break
            if text is None:
                break
            text = text.strip()
            if not text:
                continue

            now = datetime.now(UTC)
            msg_id = secrets.token_hex(8)

            self._messages.append(
                RoomMessage(
                    message_id=msg_id,
                    timestamp=now,
                    username=self._human_username,
                    display_name=self._human_username,
                    text=text,
                )
            )

            yield IncomingMessage(
                message_id=msg_id,
                user_id=self._human_user_id,
                username=self._human_username,
                display_name=self._human_username,
                text=text,
                room_id=ROOM_ID,
                kind=RoomKind.direct,
                timestamp=now,
            )

    async def _read_input(self) -> str | None:
        """Read a line from the user via prompt_toolkit."""
        assert self._session is not None
        try:
            return await self._session.prompt_async(f"{self._human_username} > ")
        except EOFError:
            return None

    async def send_message(
        self,
        room_id: str,
        text: str,
        attachments: list[dict[str, Any]] | None = None,
        *,
        thread_id: str = "",
    ) -> None:
        self._stop_reactions()
        now = datetime.now(UTC)
        msg_id = secrets.token_hex(8)
        self._messages.append(
            RoomMessage(
                message_id=msg_id,
                timestamp=now,
                username=self.username,
                display_name=self.username,
                text=text,
            )
        )
        panel = Panel(
            Markdown(text),
            title="[bold]docketeer[/bold]",
            title_align="left",
            border_style="blue",
            box=box.ROUNDED,
            padding=(0, 1),
        )
        self._console.print(panel)
        if self._on_message_sent:
            await self._on_message_sent(room_id, text)

    async def upload_file(
        self, room_id: str, file_path: str, message: str = "", *, thread_id: str = ""
    ) -> None:
        parts: list[str] = []
        if message:
            parts.append(message)
        parts.append(f"[file: {file_path}]")
        panel = Panel(
            "\n".join(parts),
            title="[bold]docketeer[/bold]",
            title_align="left",
            border_style="blue",
            box=box.ROUNDED,
            padding=(0, 1),
        )
        self._console.print(panel)

    async def fetch_attachment(self, url: str) -> bytes:
        raise ConnectionError(f"TUI client cannot fetch attachments: {url}")

    async def fetch_message(self, message_id: str) -> dict[str, Any] | None:
        for msg in self._messages:
            if msg.message_id == message_id:
                return {"_id": msg.message_id, "msg": msg.text}
        return None

    async def fetch_messages(
        self,
        room_id: str,
        *,
        before: datetime | None = None,
        after: datetime | None = None,
        count: int = 50,
    ) -> list[RoomMessage]:
        messages = self._messages
        if after:
            messages = [m for m in messages if m.timestamp > after]
        if before:
            messages = [m for m in messages if m.timestamp < before]
        return messages[-count:]

    async def list_rooms(self) -> list[RoomInfo]:
        return [
            RoomInfo(
                room_id=ROOM_ID,
                kind=RoomKind.direct,
                members=[self._human_username, self.username],
            )
        ]

    async def set_status(self, status: str, message: str = "") -> None:
        if status == "away":
            self._console.print(Text("  thinking...", style="dim italic"))
        elif log.isEnabledFor(logging.DEBUG):
            log.debug("status: %s %s", status, message)

    async def send_typing(self, room_id: str, typing: bool) -> None:
        pass

    _EMOJI_ALIASES: dict[str, str] = {
        ":lock:": ":locked:",
        ":mag:": ":magnifying_glass_tilted_left:",
    }

    def _stop_reactions(self) -> None:
        self._reaction_emojis.clear()
        self._reactions_printed = False

    async def react(self, message_id: str, emoji: str) -> None:
        emoji = self._EMOJI_ALIASES.get(emoji, emoji)
        self._reaction_emojis.append(emojize(emoji))
        display = Text("  " + " ".join(self._reaction_emojis), style="dim")
        if self._reactions_printed:
            # Move cursor up one line and clear it before reprinting
            self._console.file.write("\x1b[1A\x1b[2K")
        elif self._active_stream:
            self._active_stream.extra_lines += 1
        self._console.print(display)
        self._reactions_printed = True

    async def start_reply_stream(
        self,
        msg: IncomingMessage,
        thread_id: str,
        text: str,
    ) -> _TUIStream | None:
        self._stop_reactions()
        stream = _TUIStream()
        stream.parts.append(text)
        self._active_stream = stream
        return stream

    async def append_reply_stream(self, stream: Any, text: str) -> None:
        assert isinstance(stream, _TUIStream)
        stream.parts.append(text)
        if stream.lines_printed:
            self._reprint_stream_panel(stream)
        else:
            self._print_stream_panel(stream)

    async def stop_reply_stream(self, stream: Any) -> None:
        assert isinstance(stream, _TUIStream)
        self._active_stream = None
        text = "".join(stream.parts)
        now = datetime.now(UTC)
        msg_id = secrets.token_hex(8)
        self._messages.append(
            RoomMessage(
                message_id=msg_id,
                timestamp=now,
                username=self.username,
                display_name=self.username,
                text=text,
            )
        )
        self._reprint_stream_panel(stream)
        if self._on_message_sent:
            await self._on_message_sent(ROOM_ID, text)

    def _print_stream_panel(self, stream: _TUIStream) -> None:
        text = "".join(stream.parts)
        panel = Panel(
            Markdown(text),
            title="[bold]docketeer[/bold]",
            title_align="left",
            border_style="blue",
            box=box.ROUNDED,
            padding=(0, 1),
        )
        with self._console.capture() as capture:
            self._console.print(panel)
        output = capture.get()
        stream.lines_printed = output.count("\n")
        self._console.file.write(output)
        self._console.file.flush()

    def _reprint_stream_panel(self, stream: _TUIStream) -> None:
        total = stream.lines_printed + stream.extra_lines
        if total:
            self._console.file.write(f"\x1b[{total}A\x1b[J")
        stream.extra_lines = 0
        self._print_stream_panel(stream)

    async def unreact(self, message_id: str, emoji: str) -> None:
        pass

    async def room_slug(self, room_id: str) -> str:
        return self._human_username

    async def room_context(self, room_id: str, username: str) -> str:
        return f"Room: DM with @{username}"
