from __future__ import annotations

import re
import time
from collections import deque
from dataclasses import dataclass, field
from pathlib import Path

from rich import box as rich_box
from rich.columns import Columns
from rich.console import Console, Group
from rich.live import Live
from rich.padding import Padding
from rich.panel import Panel
from rich.rule import Rule
from rich.syntax import Syntax
from rich.table import Table
from rich.text import Text

from dont_hallucinate.runtime import is_within_workspace
from dont_hallucinate.burn import load_snark_map, pick_roast, detect_agent
from dont_hallucinate.watcher import FailureReport, tail_jsonl, to_failure_report

# ── Single colour scheme: everything uses this accent ─────────────────────────
ACCENT     = "orange1"
ACCENT_DIM = "dark_orange"
MUTED      = "bright_black"

# ── Error type → short label ──────────────────────────────────────────────────
_LABELS: dict[str, str] = {
    "invalid_flag":       "hallucinated flag",
    "missing_package":    "phantom package",
    "permission_denied":  "permission denied",
    "git_branch_missing": "ghost branch",
    "git_dirty":          "dirty working tree",
    "git_conflict":       "merge conflict",
    "npm_missing_script": "missing npm script",
    "file_not_found":     "file not found",
    "port_in_use":        "port in use",
    "syntax_error":       "syntax error",
    "network_error":      "network failure",
    "no_space":           "disk full",
    "generic_error":      "shell failure",
}

_DONT_LINES = [
    "██████╗  ██████╗ ███╗  ██╗████████╗",
    "██╔══██╗██╔═══██╗████╗ ██║╚══██╔══╝",
    "██║  ██║██║   ██║██╔██╗██║   ██║   ",
    "██║  ██║██║   ██║██║╚████║   ██║   ",
    "██████╔╝╚██████╔╝██║ ╚███║   ██║   ",
    "╚═════╝  ╚═════╝ ╚═╝  ╚══╝   ╚═╝   ",
]
_HALL_LINES = [
    "██╗  ██╗ █████╗ ██╗     ██╗     ██╗   ██╗ ██████╗██╗███╗  ██╗ █████╗ ████████╗███████╗",
    "██║  ██║██╔══██╗██║     ██║     ██║   ██║██╔════╝██║████╗ ██║██╔══██╗╚══██╔══╝██╔════╝",
    "███████║███████║██║     ██║     ██║   ██║██║     ██║██╔██╗██║███████║   ██║   █████╗  ",
    "██╔══██║██╔══██║██║     ██║     ██║   ██║██║     ██║██║╚████║██╔══██║   ██║   ██╔══╝  ",
    "██║  ██║██║  ██║███████╗███████╗╚██████╔╝╚██████╗██║██║ ╚███║██║  ██║   ██║   ███████╗",
    "╚═╝  ╚═╝╚═╝  ╚═╝╚══════╝╚══════╝ ╚═════╝  ╚═════╝╚═╝╚═╝  ╚══╝╚═╝  ╚═╝   ╚═╝   ╚══════╝",
]


@dataclass(slots=True)
class UIState:
    logs: deque[FailureReport] = field(default_factory=lambda: deque(maxlen=8))
    latest_report: FailureReport | None = None
    latest_roast: str = ""
    total_caught: int = 0
    agent: str = "unknown"


def _strip_wrapper(command: str) -> str:
    m = re.search(r"bash\s+-[lc]+\s+'(.*)'$", command, re.DOTALL)
    if m:
        return m.group(1)
    m = re.search(r'bash\s+-[lc]+\s+"(.*)"$', command, re.DOTALL)
    if m:
        return m.group(1)
    return command


def _banner() -> Text:
    t = Text(justify="left")
    for line in _DONT_LINES:
        t.append(line + "\n", style=f"bold {ACCENT}")
    for line in _HALL_LINES:
        t.append(line + "\n", style="bold white")
    return t


def _idle_screen(state: UIState) -> Panel:
    body = Group(
        _banner(),
        Text(""),
        Rule(style=MUTED),
        Text(""),
        Text(f"  watching for shell failures", style="dim"),
        Text(f"  agent: {state.agent}", style="dim"),
    )
    return Panel(
        body,
        title=f"[bold {ACCENT}]dont-hallucinate[/]",
        border_style=ACCENT_DIM,
        box=rich_box.HEAVY,
        padding=(1, 2),
    )


def _main_screen(state: UIState) -> Panel:
    report = state.latest_report
    parsed = report.parsed
    event = report.event
    verify = report.verification

    cmd = _strip_wrapper(event.command)
    label = _LABELS.get(parsed.error_type, parsed.error_type)

    # ── Top: error type label + exit code ─────────────────────────────────────
    header = Text()
    header.append(f"  {label.upper()}", style=f"bold {ACCENT}")
    header.append(f"  exit {event.exit_code}", style=f"dim")
    header.append(f"  {event.actor or 'unknown'}", style=f"dim")

    # ── Command (syntax highlighted) ──────────────────────────────────────────
    cmd_panel = Panel(
        Syntax(cmd, "bash", theme="vim", word_wrap=True, background_color="default"),
        title="[dim]command[/]",
        border_style=MUTED,
        box=rich_box.SIMPLE,
        padding=(0, 1),
    )

    # ── Fix suggestion ─────────────────────────────────────────────────────────
    fix_parts: list[tuple[str, str]] = []
    if parsed.hallucinated_flag:
        fix_parts.append(("bad flag", parsed.hallucinated_flag))
    if verify.corrected_command:
        fix_parts.append(("try", verify.corrected_command))
    elif verify.suggested_flag:
        fix_parts.append(("try flag", verify.suggested_flag))

    fix_rows = Text()
    for key, val in fix_parts:
        fix_rows.append(f"  {key:<10}", style="dim")
        fix_rows.append(f"{val}\n", style="bold white")

    # ── Roast ──────────────────────────────────────────────────────────────────
    roast_panel = Panel(
        Text(f'"{state.latest_roast}"', style=f"italic {ACCENT}"),
        border_style=ACCENT_DIM,
        box=rich_box.SIMPLE,
        padding=(0, 1),
    )

    # ── History log ───────────────────────────────────────────────────────────
    tbl = Table(
        box=rich_box.SIMPLE_HEAD,
        show_header=True,
        header_style=f"dim",
        expand=True,
        padding=(0, 1),
        show_edge=False,
    )
    tbl.add_column("time", style="dim", width=10, no_wrap=True)
    tbl.add_column("type", width=22, no_wrap=True)
    tbl.add_column("command", overflow="fold")
    tbl.add_column("exit", width=4, justify="right")

    for r in list(state.logs):
        ts = r.event.ts[11:19] if len(r.event.ts) > 18 else r.event.ts
        c = _strip_wrapper(r.event.command)[:70]
        err_label = _LABELS.get(r.parsed.error_type, r.parsed.error_type)
        is_latest = r is state.latest_report
        row_style = f"bold {ACCENT}" if is_latest else "dim"
        tbl.add_row(
            Text(ts, style="dim"),
            Text(err_label, style=row_style),
            Text(c, style=row_style),
            Text(str(r.event.exit_code), style=row_style),
        )

    body = Group(
        Padding(header, (1, 1, 0, 1)),
        Text(""),
        Padding(cmd_panel, (0, 1)),
        Padding(fix_rows, (0, 1)) if fix_parts else Text(""),
        Padding(roast_panel, (0, 1)),
        Rule(style=MUTED),
        tbl,
    )

    return Panel(
        body,
        title=f"[bold {ACCENT}]dont-hallucinate[/]  [dim]{state.total_caught} caught[/]",
        border_style=ACCENT,
        box=rich_box.HEAVY,
        padding=(0, 0),
    )


def _boot_splash(agent: str) -> None:
    """Display a brief startup splash before entering the live watch loop."""
    console = Console()

    steps = [
        ("initialising runtime", 0.3),
        ("loading snark database", 0.4),
        ("detecting agent", 0.3),
        (f"agent identified: {agent}", 0.5),
        ("arming interceptors", 0.4),
        ("watching", 0.3),
    ]

    splash = Panel(
        _banner(),
        title=f"[bold {ACCENT}]dont-hallucinate[/]",
        border_style=ACCENT_DIM,
        box=rich_box.HEAVY,
        padding=(1, 2),
    )
    console.print(splash)
    console.print()

    for label, delay in steps:
        console.print(f"  [{ACCENT_DIM}]>[/] [{ACCENT}]{label}[/]", highlight=False)
        time.sleep(delay)

    console.print()
    console.print(f"  [{MUTED}]ready — intercepting shell failures[/]")
    time.sleep(0.5)
    console.print()


def run_ui(
    stream_file: Path,
    poll_interval: float = 0.2,
    from_start: bool = False,
    workspace: Path | None = None,
) -> None:
    state = UIState(agent=detect_agent())
    snark = load_snark_map()

    _boot_splash(state.agent)

    def render() -> Panel:
        if state.latest_report is None:
            return _idle_screen(state)
        return _main_screen(state)

    with Live(render(), refresh_per_second=4, screen=False) as live:
        for event in tail_jsonl(stream_file, poll_interval=poll_interval, from_start=from_start):
            if event.exit_code == 0:
                continue
            if not is_within_workspace(event.cwd, workspace):
                continue
            report = to_failure_report(event)
            state.latest_report = report
            state.total_caught += 1
            state.latest_roast = pick_roast(
                snark,
                report.parsed.error_type,
                agent=state.agent,
            )
            state.logs.append(report)
            live.update(render())
