import json
import logging
from pathlib import Path
from typing import Any, Dict, List

from aitop.__main__ import AITop
from aitop.core.gpu.base import GPUInfo
from aitop.core.system.cpu import CPUStats
from aitop.core.system.memory import MemoryStats
from aitop.data_collector import SystemData


class _ModalStub:
    def __init__(self) -> None:
        self.status_calls: List[Dict[str, Any]] = []
        self.error_calls: List[Dict[str, Any]] = []

    def render_status(
        self, message: str, success: bool = True, duration_ms: int = 0
    ) -> None:
        self.status_calls.append(
            {"message": message, "success": success, "duration_ms": duration_ms}
        )

    def render_error(self, message: str, duration_ms: int = 0) -> None:
        self.error_calls.append({"message": message, "duration_ms": duration_ms})


def _build_app_stub(system_data: SystemData) -> AITop:
    app = object.__new__(AITop)
    app.selected_tab = 1
    app.sort_by = "cpu_percent"
    app.sort_reverse = True
    app.process_filter_query = "name:python"
    app.process_tree_mode = False
    app.history_size = 3
    app.metric_history = {"cpu": [], "memory": [], "gpu": []}
    app.system_data = system_data
    app.modal = _ModalStub()
    app.logger = logging.getLogger("test.aitop")
    return app


def _build_system_data(
    cpu_percent: float = 25.0, memory_percent: float = 40.0
) -> SystemData:
    cpu_stats = CPUStats(
        vendor="TestVendor",
        model="Test CPU",
        total_percent=cpu_percent,
        core_percents=[cpu_percent],
        current_freq=3000.0,
        min_freq=1000.0,
        max_freq=4000.0,
        temperature=50.0,
        load_1min=0.5,
        load_5min=0.4,
        load_15min=0.3,
    )
    memory_stats = MemoryStats(
        total=16 * 1024**3,
        used=8 * 1024**3,
        free=8 * 1024**3,
        percent=memory_percent,
        swap_total=4 * 1024**3,
        swap_used=1 * 1024**3,
        swap_free=3 * 1024**3,
        swap_percent=25.0,
    )
    gpu = GPUInfo(
        index=0,
        name="Test GPU",
        utilization=60.0,
        memory_used=4096.0,
        memory_total=8192.0,
        temperature=55.0,
        power_draw=120.0,
        power_limit=200.0,
        processes=[{"pid": 111, "name": "python train.py", "memory": 1024.0}],
    )
    return SystemData(
        gpu_info=[(gpu, "nvidia")],
        gpu_processes=[{"pid": 111, "name": "python train.py"}],
        processes=[{"pid": 111, "name": "python", "cpu_percent": 10.0}],
        memory_stats=memory_stats,
        memory_types={"cached": 1024.0},
        cpu_stats=cpu_stats,
        primary_vendor="nvidia",
        timestamp=123.0,
    )


def test_record_history_sample_truncates_to_history_size() -> None:
    app = _build_app_stub(_build_system_data(cpu_percent=10.0, memory_percent=20.0))

    for idx in range(5):
        app.system_data = _build_system_data(
            cpu_percent=10.0 + idx, memory_percent=20.0 + idx
        )
        app._record_history_sample()

    assert app.metric_history["cpu"] == [12.0, 13.0, 14.0]
    assert app.metric_history["memory"] == [22.0, 23.0, 24.0]
    assert app.metric_history["gpu"] == [60.0, 60.0, 60.0]


def test_build_help_lines_adds_tree_hint_only_on_ai_tab() -> None:
    app = _build_app_stub(_build_system_data())

    app.selected_tab = 1
    app.process_tree_mode = True
    ai_lines = app._build_help_lines()
    assert any("theme settings" in line for line in ai_lines)
    assert any("tree mode (currently ON)" in line for line in ai_lines)

    app.selected_tab = 2
    non_ai_lines = app._build_help_lines()
    assert any("theme settings" in line for line in non_ai_lines)
    assert not any("tree mode" in line for line in non_ai_lines)


def test_build_snapshot_payload_contains_ui_state_and_gpu_vendor() -> None:
    app = _build_app_stub(_build_system_data())
    app.metric_history = {"cpu": [1.0], "memory": [2.0], "gpu": [3.0]}

    payload = app._build_snapshot_payload()

    assert payload["ui_state"]["filter_query"] == "name:python"
    assert payload["ui_state"]["tree_mode"] is False
    assert payload["system"]["gpus"][0]["vendor"] == "nvidia"
    assert payload["history"]["cpu"] == [1.0]


def test_build_snapshot_payload_schema_contract() -> None:
    app = _build_app_stub(_build_system_data())
    app.metric_history = {"cpu": [1.0, 2.0], "memory": [3.0], "gpu": [4.0, 5.0]}

    payload = app._build_snapshot_payload()

    assert set(payload.keys()) == {
        "timestamp",
        "version",
        "ui_state",
        "system",
        "history",
    }
    assert isinstance(payload["timestamp"], str)
    assert isinstance(payload["version"], str)

    assert set(payload["ui_state"].keys()) == {
        "selected_tab",
        "sort_by",
        "sort_reverse",
        "filter_query",
        "tree_mode",
    }
    assert isinstance(payload["ui_state"]["selected_tab"], int)
    assert isinstance(payload["ui_state"]["sort_by"], str)
    assert isinstance(payload["ui_state"]["sort_reverse"], bool)
    assert isinstance(payload["ui_state"]["filter_query"], str)
    assert isinstance(payload["ui_state"]["tree_mode"], bool)

    system = payload["system"]
    assert set(system.keys()) == {
        "primary_vendor",
        "cpu",
        "memory",
        "memory_types",
        "gpus",
        "processes",
    }
    assert isinstance(system["primary_vendor"], str)
    assert isinstance(system["cpu"], dict)
    assert isinstance(system["memory"], dict)
    assert isinstance(system["memory_types"], dict)
    assert isinstance(system["gpus"], list)
    assert isinstance(system["processes"], list)
    assert system["gpus"]
    first_gpu = system["gpus"][0]
    assert {
        "index",
        "name",
        "utilization",
        "memory_used",
        "memory_total",
        "vendor",
    } <= set(first_gpu.keys())

    assert set(payload["history"].keys()) == {"cpu", "memory", "gpu"}
    assert all(isinstance(series, list) for series in payload["history"].values())


def test_export_snapshot_writes_file_and_reports_success(monkeypatch, tmp_path) -> None:
    app = _build_app_stub(_build_system_data())
    app.metric_history = {"cpu": [1.0, 2.0], "memory": [3.0], "gpu": [4.0]}

    monkeypatch.setattr(Path, "cwd", lambda: tmp_path)

    app._export_snapshot()

    written = sorted(tmp_path.glob("aitop-snapshot-*.json"))
    assert len(written) == 1
    payload = json.loads(written[0].read_text(encoding="utf-8"))
    assert payload["system"]["primary_vendor"] == "nvidia"
    assert payload["history"]["gpu"] == [4.0]
    assert app.modal.status_calls
    assert app.modal.status_calls[0]["message"].startswith("Snapshot saved:")


def test_export_snapshot_reports_error_when_payload_build_fails() -> None:
    app = _build_app_stub(_build_system_data())
    app._build_snapshot_payload = lambda: (_ for _ in ()).throw(
        RuntimeError("snapshot broken")
    )

    app._export_snapshot()

    assert app.modal.error_calls
    assert "snapshot broken" in app.modal.error_calls[0]["message"]
