import aitop.ui.display as display_module


class _FakePad:
    def __init__(self) -> None:
        self.calls: list[tuple[int, int, str, int]] = []

    def addstr(self, y: int, x: int, text: str, attr: int = 0) -> None:
        self.calls.append((y, x, text, attr))

    def noutrefresh(
        self,
        pminrow: int,
        pmincol: int,
        sminrow: int,
        smincol: int,
        smaxrow: int,
        smaxcol: int,
    ) -> None:
        del pminrow, pmincol, sminrow, smincol, smaxrow, smaxcol

    def refresh(self) -> None:
        return None

    def clear(self) -> None:
        return None

    def keypad(self, _flag: bool) -> None:
        return None


class _FakeStdScr:
    def __init__(self, height: int, width: int) -> None:
        self._height = height
        self._width = width

    def getmaxyx(self) -> tuple[int, int]:
        return self._height, self._width

    def nodelay(self, _flag: int) -> None:
        return None

    def timeout(self, _ms: int) -> None:
        return None

    def keypad(self, _flag: bool) -> None:
        return None

    def clear(self) -> None:
        return None

    def refresh(self) -> None:
        return None


def _make_display(monkeypatch, height: int, width: int) -> display_module.Display:
    stdscr = _FakeStdScr(height, width)
    fake_pad = _FakePad()

    monkeypatch.setattr(display_module.curses, "start_color", lambda: None)
    monkeypatch.setattr(display_module.curses, "use_default_colors", lambda: None)
    monkeypatch.setattr(display_module.curses, "cbreak", lambda: None)
    monkeypatch.setattr(display_module.curses, "noecho", lambda: None)
    monkeypatch.setattr(display_module.curses, "use_env", lambda _flag: None)
    monkeypatch.setattr(display_module.curses, "def_prog_mode", lambda: None)
    monkeypatch.setattr(display_module.curses, "curs_set", lambda _v: 0)
    monkeypatch.setattr(display_module.curses, "A_NORMAL", 0)
    monkeypatch.setattr(display_module.curses, "init_pair", lambda *_args: None)
    monkeypatch.setattr(display_module.curses, "doupdate", lambda: None)
    monkeypatch.setattr(display_module.curses, "can_change_color", lambda: False)
    monkeypatch.setattr(display_module.curses, "newpad", lambda _h, _w: fake_pad)

    display = display_module.Display(stdscr)
    display.pad = fake_pad
    display.visible_height = height
    display.visible_width = width
    return display


def test_safe_addstr_ignores_negative_coords(monkeypatch) -> None:
    display = _make_display(monkeypatch, height=10, width=20)

    assert display.safe_addstr(-1, 0, "oops") is False
    assert display.safe_addstr(0, -1, "oops") is False
    assert display.pad.calls == []


def test_safe_addstr_truncates_to_visible_width(monkeypatch) -> None:
    display = _make_display(monkeypatch, height=5, width=8)

    rendered = display.safe_addstr(0, 6, "abcdef")

    assert rendered is True
    assert display.pad.calls[0][2] == "ab"


def test_refresh_handles_zero_visible_dimensions(monkeypatch) -> None:
    display = _make_display(monkeypatch, height=0, width=0)
    display.visible_height = 0
    display.visible_width = 0

    display.refresh()

    assert display.get_performance_stats()["last_render_time"] >= 0.0


def test_safe_addstr_clears_trailing_chars_when_text_shrinks(monkeypatch) -> None:
    display = _make_display(monkeypatch, height=4, width=20)

    assert display.safe_addstr(0, 0, "abcdef", 1) is True
    assert display.safe_addstr(0, 0, "abc", 1) is True

    assert display.pad.calls[-1][2] == "abc   "


def test_end_frame_clears_cells_not_touched_in_new_frame(monkeypatch) -> None:
    display = _make_display(monkeypatch, height=4, width=20)
    display.begin_frame()
    display.safe_addstr(0, 0, "alive", 1)
    display.end_frame()

    calls_after_first_frame = len(display.pad.calls)

    display.begin_frame()
    display.end_frame()

    assert len(display.pad.calls) == calls_after_first_frame + 1
    assert display.pad.calls[-1][2] == "     "


def test_end_frame_does_not_erase_overlapping_new_segments(monkeypatch) -> None:
    display = _make_display(monkeypatch, height=4, width=40)

    display.begin_frame()
    display.safe_addstr(0, 0, "left-segment", 1)
    display.safe_addstr(0, 12, "right", 2)
    display.end_frame()

    baseline_calls = len(display.pad.calls)

    display.begin_frame()
    display.safe_addstr(0, 0, "left-segment right", 3)
    display.end_frame()

    new_calls = display.pad.calls[baseline_calls:]
    assert not any(
        y == 0 and x == 12 and text == "     " for y, x, text, _ in new_calls
    )


def test_get_color_prefers_metric_roles_when_present(monkeypatch) -> None:
    display = _make_display(monkeypatch, height=4, width=20)
    monkeypatch.setattr(display_module.curses, "A_BOLD", 8)
    display.themes = {
        "default": {
            "colors": {
                "normal": {"fg": "GREEN", "bg": -1, "attrs": []},
                "warning": {"fg": "YELLOW", "bg": -1, "attrs": []},
                "critical": {"fg": "RED", "bg": -1, "attrs": []},
                "metric_normal": {"fg": "CYAN", "bg": -1, "attrs": []},
                "metric_warning": {"fg": "MAGENTA", "bg": -1, "attrs": []},
                "metric_critical": {"fg": "WHITE", "bg": -1, "attrs": []},
            },
            "progress_bar": {"thresholds": {"warning": 60, "critical": 80}},
        }
    }
    display.current_theme = "default"
    display.theme_attrs = {
        "normal": 1,
        "warning": 2,
        "critical": 3,
        "metric_normal": 11,
        "metric_warning": 12,
        "metric_critical": 13,
    }

    assert display.get_color(25.0) == (11 | 8)
    assert display.get_color(70.0) == (12 | 8)
    assert display.get_color(95.0) == (13 | 8)


def test_get_color_uses_accent_fallback_for_normal_metrics(monkeypatch) -> None:
    display = _make_display(monkeypatch, height=4, width=20)
    monkeypatch.setattr(display_module.curses, "A_BOLD", 8)
    display.themes = {
        "default": {
            "colors": {
                "normal": {"fg": "GREEN", "bg": -1, "attrs": []},
                "warning": {"fg": "YELLOW", "bg": -1, "attrs": []},
                "critical": {"fg": "RED", "bg": -1, "attrs": []},
                "info": {"fg": "CYAN", "bg": -1, "attrs": []},
            },
            "progress_bar": {"thresholds": {"warning": 60, "critical": 80}},
        }
    }
    display.current_theme = "default"
    display.theme_attrs = {"normal": 1, "warning": 2, "critical": 3, "info": 9}

    assert display.get_color(10.0) == (9 | 8)
    assert display.get_color(75.0) == (2 | 8)


def test_create_bar_uses_blank_unfilled_segment_for_graphite_rectangles(
    monkeypatch,
) -> None:
    display = _make_display(monkeypatch, height=4, width=40)
    display.get_color = lambda _value: 0  # type: ignore[method-assign]
    display.current_theme = "graphite_modern"
    display.themes = {
        "default": {
            "progress_bar": {"filled": "█", "empty": "░"},
            "colors": {},
        },
        "graphite_modern": {
            "progress_bar": {"filled": "▮", "empty": "▯"},
            "colors": {},
        },
    }

    bar, color = display.create_bar(40.0, 10)

    assert bar == ("▮" * 4 + " " * 6)
    assert len(bar) == 10
    assert color == 0


def test_create_bar_keeps_non_graphite_unfilled_glyphs(monkeypatch) -> None:
    display = _make_display(monkeypatch, height=4, width=40)
    display.get_color = lambda _value: 0  # type: ignore[method-assign]
    display.current_theme = "default"
    display.themes = {
        "default": {
            "progress_bar": {"filled": "█", "empty": "░"},
            "colors": {},
        }
    }

    bar, _color = display.create_bar(40.0, 10)

    assert bar == ("█" * 4 + "░" * 6)


def test_create_bar_graphite_rectangles_respect_clamping(monkeypatch) -> None:
    display = _make_display(monkeypatch, height=4, width=40)
    display.get_color = lambda _value: 0  # type: ignore[method-assign]
    display.current_theme = "graphite_modern"
    display.themes = {
        "default": {
            "progress_bar": {"filled": "█", "empty": "░"},
            "colors": {},
        },
        "graphite_modern": {
            "progress_bar": {"filled": "▮", "empty": "▯"},
            "colors": {},
        },
    }

    bar_low, _color_low = display.create_bar(-5.0, 8)
    bar_high, _color_high = display.create_bar(250.0, 8)

    assert bar_low == (" " * 8)
    assert bar_high == ("▮" * 8)
