"""pytest plugin — run pyos UI tests in headful mode.

Activate with:
    pytest --headful
    pytest --headful tests/test_timeline.py -k scroll
    FRAME_DELAY=0.1 pytest --headful

Any project that installs pyos gets this flag automatically via the
pytest11 entry point.
"""

import os
import sys
import time

from pyos.Attrs import BOLD, DIM, UNDERLINE, REVERSE, Style
from pyos.testing.harness import MockScreen


# ── ANSI color name → SGR code mapping ───────────────────────────────

_FG_COLORS = {
    "black": "30", "red": "31", "green": "32", "yellow": "33",
    "blue": "34", "magenta": "35", "cyan": "36", "white": "37",
}
_BG_COLORS = {
    "black": "40", "red": "41", "green": "42", "yellow": "43",
    "blue": "44", "magenta": "45", "cyan": "46", "white": "47",
}


# ── ANSI attribute mapping ────────────────────────────────────────────

def _ansi(attr):
    codes = []
    if attr & BOLD:      codes.append("1")
    if attr & DIM:       codes.append("2")
    if attr & UNDERLINE: codes.append("4")
    if attr & REVERSE:   codes.append("7")
    if isinstance(attr, Style):
        fg = attr.fg
        bg = attr.bg
        if fg is not None:
            if isinstance(fg, str) and fg in _FG_COLORS:
                codes.append(_FG_COLORS[fg])
            elif isinstance(fg, int):
                codes.append(f"38;5;{fg}")
            elif isinstance(fg, tuple):
                codes.append(f"38;2;{fg[0]};{fg[1]};{fg[2]}")
        if bg is not None:
            if isinstance(bg, str) and bg in _BG_COLORS:
                codes.append(_BG_COLORS[bg])
            elif isinstance(bg, int):
                codes.append(f"48;5;{bg}")
            elif isinstance(bg, tuple):
                codes.append(f"48;2;{bg[0]};{bg[1]};{bg[2]}")
    return f"\033[{';'.join(codes)}m" if codes else "\033[0m"


# ── Terminal renderer ─────────────────────────────────────────────────

class Renderer:
    """Renders MockScreen buffers to the real terminal via ANSI escapes."""

    def __init__(self, delay=0.04):
        self.delay = delay
        self.test_name = ""
        self.passed = 0
        self.failed = 0
        self._tty = open("/dev/tty", "w")

    def enter(self):
        """Alt screen, hide cursor, clear."""
        self._write("\033[?1049h\033[?25l\033[2J")

    def leave(self):
        """Restore terminal."""
        self._write("\033[?1049l\033[?25h")
        self._tty.close()

    def _write(self, s):
        self._tty.write(s)
        self._tty.flush()

    def render(self, screen):
        buf = []
        w = screen.cols

        # cursor home
        buf.append("\033[H")

        # ── title bar ──
        name = self.test_name
        stats = f" {self.passed}\u2713 {self.failed}\u2717 "
        space = w + 2 - len(stats)
        if len(name) + 2 > space:
            name = name[: space - 5] + "\u2026"
        pad = max(0, space - len(name) - 1)
        buf.append(f"\033[1;97;44m {name}{' ' * pad}{stats}\033[0m\033[K\n")

        # ── top border ──
        buf.append(f"\033[36m\u250c{'\u2500' * w}\u2510\033[0m\033[K\n")

        # ── screen rows ──
        for r in range(screen.rows):
            buf.append("\033[36m\u2502\033[0m")
            prev = -1
            for c in range(w):
                a = screen.attr_buffer[r][c]
                if a != prev:
                    buf.append(_ansi(a))
                    prev = a
                buf.append(screen.buffer[r][c])
            buf.append("\033[0m\033[36m\u2502\033[0m\033[K\n")

        # ── bottom border ──
        buf.append(f"\033[36m\u2514{'\u2500' * w}\u2518\033[0m\033[K\n")

        # clear anything below (handles screen-size changes between tests)
        buf.append("\033[J")

        self._tty.write("".join(buf))
        self._tty.flush()

        if self.delay > 0:
            time.sleep(self.delay)


# ── Monkeypatch plumbing ─────────────────────────────────────────────

_renderer = None
_orig_refresh = MockScreen.refresh


def _visual_refresh(self):
    _orig_refresh(self)
    if _renderer is not None:
        _renderer.render(self)


# ── Pytest hooks ─────────────────────────────────────────────────────

def pytest_addoption(parser):
    parser.addoption(
        "--headful",
        action="store_true",
        default=False,
        help="Render MockScreen to the terminal in real time.",
    )


def pytest_configure(config):
    global _renderer

    if not config.getoption("headful", default=False):
        return

    delay = float(os.environ.get("FRAME_DELAY", "0.04"))
    _renderer = Renderer(delay=delay)
    MockScreen.refresh = _visual_refresh
    _renderer.enter()

    config._headful_renderer = _renderer
    config.pluginmanager.register(_HeadfulReporter(_renderer), "headful-reporter")


def pytest_unconfigure(config):
    global _renderer

    renderer = getattr(config, "_headful_renderer", None)
    if renderer is None:
        return

    time.sleep(0.3)
    _renderer = None
    MockScreen.refresh = _orig_refresh
    renderer.leave()

    reporter = config.pluginmanager.get_plugin("headful-reporter")
    if reporter:
        _print_summary(reporter)


class _HeadfulReporter:
    """Tracks pass/fail and updates the title bar during headful runs."""

    def __init__(self, renderer):
        self.renderer = renderer
        self.failures = []

    def pytest_runtest_logstart(self, nodeid, location):
        self.renderer.test_name = nodeid

    def pytest_runtest_logreport(self, report):
        if report.when == "call":
            if report.passed:
                self.renderer.passed += 1
            elif report.failed:
                self.renderer.failed += 1
                text = getattr(report, "longreprtext", "") or ""
                self.failures.append((report.nodeid, text))


def _print_summary(reporter):
    p = reporter.renderer.passed
    f = reporter.renderer.failed
    print()
    if f == 0:
        print(f"\033[32m \u2713 {p} passed\033[0m")
    else:
        print(f"\033[32m \u2713 {p} passed\033[0m  \033[31m\u2717 {f} failed\033[0m")
        for nodeid, text in reporter.failures:
            print(f"\n\033[31m  FAIL {nodeid}\033[0m")
            for line in text.strip().splitlines()[-4:]:
                print(f"    {line}")
    print()
