"""Terminal UI helpers for Janus Labs CLI output."""

import sys
import threading
import time

# ANSI escape sequences
CURSOR_UP = "\033[{}A"
CLEAR_LINE = "\033[2K"


def _supports_unicode_ui() -> bool:
    """Return whether the active stdout encoding can safely print UI glyphs."""
    encoding = sys.stdout.encoding or "utf-8"
    try:
        "○◉✓✗█░═—".encode(encoding)
        return True
    except (UnicodeEncodeError, LookupError):
        return False


UNICODE_UI = _supports_unicode_ui()
PENDING_ICON = "○" if UNICODE_UI else "o"
RUNNING_ICON = "◉" if UNICODE_UI else "*"
DONE_ICON = "✓" if UNICODE_UI else "OK"
FAILED_ICON = "✗" if UNICODE_UI else "X"
BAR_FILLED = "█" if UNICODE_UI else "#"
BAR_EMPTY = "░" if UNICODE_UI else "-"
BOX_RULE = "═" if UNICODE_UI else "="
HEADER_DASH = "—" if UNICODE_UI else "-"


def is_ansi_capable() -> bool:
    """Check if the current terminal supports ANSI escape codes."""
    if not sys.stdout.isatty():
        return False

    import os

    if os.environ.get("TERM") == "dumb" or os.environ.get("NO_COLOR"):
        return False

    if sys.platform == "win32":
        try:
            import ctypes

            kernel32 = ctypes.windll.kernel32
            handle = kernel32.GetStdHandle(-11)  # STD_OUTPUT_HANDLE
            mode = ctypes.c_ulong()
            kernel32.GetConsoleMode(handle, ctypes.byref(mode))
            kernel32.SetConsoleMode(handle, mode.value | 0x0004)
            return True
        except Exception:
            return False

    return True


def _format_elapsed(seconds: int) -> str:
    """Format elapsed seconds for the progress board and summary card."""
    if seconds >= 60:
        return f"{seconds // 60}m {seconds % 60}s"
    return f"{seconds}s"


def _short_behavior_id(behavior_id: str) -> str:
    """Collapse verbose behavior IDs to the BHV-001 form used in the UI sketch."""
    parts = behavior_id.split("-")
    if len(parts) >= 2 and parts[0] == "BHV":
        return "-".join(parts[:2])
    return behavior_id


def _axis_key(behavior_id: str) -> str:
    """Normalize a full behavior ID to its axis prefix, e.g. BHV-001."""
    parts = behavior_id.split("-")
    if len(parts) >= 2 and parts[0] == "BHV":
        return "-".join(parts[:2])
    return behavior_id


class ProgressBoard:
    """Multi-line progress display for behavior execution.

    ANSI-capable terminals show an in-place board with a live timer for the
    active behavior. Dumb or piped terminals fall back to sequential updates.
    """

    def __init__(self, behavior_ids: list[str], behavior_names: list[str]):
        self.behaviors = list(zip(behavior_ids, behavior_names))
        self.total = len(self.behaviors)
        self.states: dict[str, str] = {bid: "pending" for bid, _ in self.behaviors}
        self.times: dict[str, str] = {}
        self.ansi = is_ansi_capable()
        self._timer_thread: threading.Thread | None = None
        self._timer_stop = threading.Event()
        self._active_behavior: str | None = None
        self._active_start = 0.0
        self._printed = False
        self._lock = threading.RLock()
        self._indices = {bid: index for index, (bid, _) in enumerate(self.behaviors, start=1)}

    def _format_line(self, index: int, behavior_id: str, name: str) -> str:
        state = self.states[behavior_id]
        display_name = f"{_short_behavior_id(behavior_id)} {name}"[:37].ljust(37)
        prefix = f"  [{index + 1}/{self.total}] {display_name}"

        if state == "pending":
            return f"{prefix} {PENDING_ICON} pending"
        if state == "running":
            elapsed = self.times.get(behavior_id, "")
            return f"{prefix} {RUNNING_ICON} running... {elapsed}"
        if state == "done":
            elapsed = self.times.get(behavior_id, "")
            return f"{prefix} {DONE_ICON} done ({elapsed})"
        if state == "failed":
            elapsed = self.times.get(behavior_id, "")
            return f"{prefix} {FAILED_ICON} failed ({elapsed})"
        return prefix

    def _render_all(self) -> None:
        """Render or redraw the full ANSI progress board."""
        with self._lock:
            if self.ansi and self._printed:
                sys.stdout.write(CURSOR_UP.format(self.total))

            for i, (behavior_id, name) in enumerate(self.behaviors):
                line = self._format_line(i, behavior_id, name)
                if self.ansi:
                    sys.stdout.write(CLEAR_LINE + line + "\n")
                else:
                    print(line)

            sys.stdout.flush()
            self._printed = True

    def render(self) -> None:
        """Draw the initial board on ANSI terminals."""
        if self.ansi:
            self._render_all()

    def start_behavior(self, behavior_id: str) -> None:
        """Mark a behavior as running and start a background elapsed timer."""
        with self._lock:
            self.states[behavior_id] = "running"
            self._active_behavior = behavior_id
            self._active_start = time.perf_counter()
            self.times[behavior_id] = "0s"

        if self.ansi:
            self._render_all()
            self._timer_stop.clear()
            self._timer_thread = threading.Thread(target=self._tick, daemon=True)
            self._timer_thread.start()
        else:
            index = self._indices[behavior_id]
            name = next(name for bid, name in self.behaviors if bid == behavior_id)
            display_name = f"{_short_behavior_id(behavior_id)} {name}"
            print(f"  [{index}/{self.total}] {display_name} ...", flush=True)

    def finish_behavior(self, behavior_id: str, success: bool, duration_ms: int) -> None:
        """Mark a behavior as complete and stop the elapsed timer."""
        self._timer_stop.set()
        if self._timer_thread:
            self._timer_thread.join(timeout=1)
            self._timer_thread = None

        duration = _format_elapsed(max(0, duration_ms // 1000))
        with self._lock:
            self.times[behavior_id] = duration
            self.states[behavior_id] = "done" if success else "failed"
            self._active_behavior = None

        if self.ansi:
            self._render_all()
        else:
            index = self._indices[behavior_id]
            name = next(name for bid, name in self.behaviors if bid == behavior_id)
            display_name = f"{_short_behavior_id(behavior_id)} {name}"
            if success:
                print(f"  [{index}/{self.total}] {display_name} {DONE_ICON} ({duration})")
            else:
                print(f"  [{index}/{self.total}] {display_name} {FAILED_ICON} ({duration})")

    def _tick(self) -> None:
        """Update the elapsed time once per second while a behavior is running."""
        while not self._timer_stop.is_set():
            with self._lock:
                active_behavior = self._active_behavior
                active_start = self._active_start

            if active_behavior and self.ansi:
                elapsed = int(time.perf_counter() - active_start)
                with self._lock:
                    self.times[active_behavior] = _format_elapsed(elapsed)
                self._render_all()

            self._timer_stop.wait(1.0)


def render_bar(score: float, width: int = 12) -> str:
    """Render a score bar for 0-100 values."""
    clamped = max(0.0, min(100.0, score))
    filled = round(clamped / 100 * width)
    empty = width - filled
    return BAR_FILLED * filled + BAR_EMPTY * empty


def render_score_card(
    agent_name: str,
    model_name: str | None,
    headline_score: float,
    grade: str,
    behavior_scores: list[dict],
    baseline_delta: float | None = None,
    passed_count: int = 0,
    total_count: int = 0,
    duration_ms: int = 0,
) -> None:
    """Render the polished score summary card for a full CLI run."""
    scores_map = {
        _axis_key(str(behavior["behavior_id"])): float(behavior["score"])
        for behavior in behavior_scores
        if behavior.get("behavior_id") and behavior.get("score") is not None
    }

    code_quality_keys = ("BHV-001", "BHV-002")
    error_resilience_keys = ("BHV-003", "BHV-004")

    code_quality_scores = [scores_map[key] for key in code_quality_keys if key in scores_map]
    error_resilience_scores = [scores_map[key] for key in error_resilience_keys if key in scores_map]

    code_quality_avg = (
        sum(code_quality_scores) / len(code_quality_scores) if code_quality_scores else 0.0
    )
    error_resilience_avg = (
        sum(error_resilience_scores) / len(error_resilience_scores) if error_resilience_scores else 0.0
    )

    agent_label = agent_name if not model_name else f"{agent_name} / {model_name}"
    duration_str = _format_elapsed(max(0, duration_ms // 1000))
    box = BOX_RULE * 46

    print()
    print(f"  {box}")
    print(f"  {agent_label} {HEADER_DASH} Score: {headline_score:.1f} ({grade})")
    print()
    print(f"  Code Quality:     {render_bar(code_quality_avg)}  {code_quality_avg:.1f}")
    print(f"  Error Resilience: {render_bar(error_resilience_avg)}  {error_resilience_avg:.1f}")
    print()
    print(f"  Behaviors: {passed_count}/{total_count} passed  |  Duration: {duration_str}")

    if baseline_delta is not None:
        sign = "+" if baseline_delta >= 0 else ""
        print(f"  vs. vanilla baseline: {sign}{baseline_delta:.1f} points")

    print(f"  {box}")
