import curses
import logging
from typing import Any, List, Optional

from aitop.__main__ import AITop


class _StdscrKeyStub:
    def __init__(self, keys: List[int]) -> None:
        self._keys = list(keys)

    def getch(self) -> int:
        if self._keys:
            return self._keys.pop(0)
        return curses.ERR


class _DisplayStub:
    def __init__(self, keys: List[int]) -> None:
        self.stdscr = _StdscrKeyStub(keys)
        self.force_redraw_called = False
        self.resize_called = False
        self.themes = {"default": {}, "graphite_modern": {}}
        self.current_theme = "default"
        self.reload_theme_calls: List[str] = []

    def force_redraw(self) -> None:
        self.force_redraw_called = True

    def handle_resize(self) -> None:
        self.resize_called = True

    def get_dimensions(self) -> tuple[int, int]:
        return (40, 120)

    def reload_theme(self, theme_name: str) -> bool:
        self.reload_theme_calls.append(theme_name)
        self.current_theme = theme_name
        return True


class _ModalStub:
    def __init__(self) -> None:
        self.status_messages: List[str] = []
        self.error_messages: List[str] = []
        self.theme_choice: Optional[str] = None
        self.theme_menu_calls: List[tuple[List[str], str]] = []

    def render_status(
        self, message: str, success: bool = True, duration_ms: int = 0
    ) -> None:
        del success, duration_ms
        self.status_messages.append(message)

    def render_error(self, message: str, duration_ms: int = 0) -> None:
        del duration_ms
        self.error_messages.append(message)

    def render_theme_menu(
        self, theme_names: List[str], current_theme: str
    ) -> Optional[str]:
        self.theme_menu_calls.append((list(theme_names), current_theme))
        return self.theme_choice


class _TabsStub:
    def handle_tab_input(
        self, key: int, current_tab: int, custom_tabs: Optional[List[str]] = None
    ) -> int:
        del key
        del custom_tabs
        return current_tab


def _build_input_app(keys: List[int], selected_tab: int = 1) -> AITop:
    app = object.__new__(AITop)
    app.input_poll_interval = 0.0
    app.last_input_poll = 0.0
    app.display = _DisplayStub(keys)
    app.needs_redraw = False
    app.running = True
    app.logger = logging.getLogger("test.aitop.input")
    app.selected_tab = selected_tab
    app.sort_by = "cpu_percent"
    app.sort_reverse = True
    app.scroll_position = 7
    app.selected_process_index = 5
    app.selected_gpu_process_index = 4
    app.selected_overview_process_index = 3
    app.filter_input_mode = False
    app.process_filter_query = ""
    app.process_tree_mode = False
    app.system_data = None
    app.modal = _ModalStub()
    app.tabs = _TabsStub()
    return app


def test_handle_input_t_toggles_tree_mode_and_resets_selection() -> None:
    app = _build_input_app([ord("t")], selected_tab=1)

    app.handle_input()

    assert app.process_tree_mode is True
    assert app.scroll_position == 0
    assert app.selected_process_index == 0
    assert app.modal.status_messages == ["Process tree mode enabled"]


def test_handle_input_question_mark_calls_help_overlay(monkeypatch: Any) -> None:
    app = _build_input_app([ord("?")], selected_tab=1)
    called = {"value": False}

    monkeypatch.setattr(
        app, "_show_help_overlay", lambda: called.__setitem__("value", True)
    )

    app.handle_input()

    assert called["value"] is True


def test_handle_input_shift_s_calls_snapshot_export(monkeypatch: Any) -> None:
    app = _build_input_app([ord("S")], selected_tab=1)
    called = {"value": False}

    monkeypatch.setattr(
        app, "_export_snapshot", lambda: called.__setitem__("value", True)
    )

    app.handle_input()

    assert called["value"] is True


def test_handle_input_slash_enters_filter_mode() -> None:
    app = _build_input_app([ord("/")], selected_tab=1)

    app.handle_input()

    assert app.filter_input_mode is True


def test_handle_input_escape_clears_filter_state() -> None:
    app = _build_input_app([27], selected_tab=1)
    app.filter_input_mode = True
    app.process_filter_query = "name:python"
    app.scroll_position = 9
    app.selected_process_index = 6
    app.selected_gpu_process_index = 2

    app.handle_input()

    assert app.filter_input_mode is False
    assert app.process_filter_query == ""
    assert app.scroll_position == 0
    assert app.selected_process_index == 0
    assert app.selected_gpu_process_index == 0


def test_handle_input_o_applies_selected_theme() -> None:
    app = _build_input_app([ord("o")], selected_tab=1)
    app.modal.theme_choice = "graphite_modern"

    app.handle_input()

    assert app.display.reload_theme_calls == ["graphite_modern"]
    assert app.display.current_theme == "graphite_modern"
    assert app.display.force_redraw_called is True
    assert app.modal.status_messages[-1] == "Theme set to graphite_modern"
    assert app.modal.theme_menu_calls == [(["default", "graphite_modern"], "default")]


def test_handle_input_o_cancel_does_not_change_theme() -> None:
    app = _build_input_app([ord("o")], selected_tab=1)
    app.modal.theme_choice = None

    app.handle_input()

    assert app.display.reload_theme_calls == []
    assert app.display.force_redraw_called is False
    assert app.modal.status_messages == []
