from __future__ import annotations

import sys
from pathlib import Path
from typing import Callable

sys.path.insert(0, str(Path(__file__).resolve().parents[1]))

from ui.alerts_tab import AlertsTab, _FallbackGtk
from ui.view_model import AlertsTabVM, AlertVM, DashboardCallbacks, SummaryLineVM


def _callbacks(
    calls: list[tuple[object, ...]],
    *,
    on_dismiss_alert: Callable[[str, str, str], None] | None = None,
) -> DashboardCallbacks:
    return DashboardCallbacks(
        on_set_default=lambda tool, slug: calls.append(("set_default", tool, slug)),
        on_reload=lambda tool, slug: calls.append(("reload", tool, slug)),
        on_repair=lambda tool, slug: calls.append(("repair", tool, slug)),
        on_rename=lambda tool, slug: calls.append(("rename", tool, slug)),
        on_remove=lambda tool, slug: calls.append(("remove", tool, slug)),
        on_add=lambda tool: calls.append(("add", tool)),
        on_open_settings=lambda: calls.append(("open_settings",)),
        on_set_limits=lambda tool, slug: calls.append(("set_limits", tool, slug)),
        on_tab_changed=lambda tab_id: calls.append(("tab_changed", tab_id)),
        on_dismiss_alert=on_dismiss_alert or (
            lambda kind, tool, slug: calls.append(("dismiss", kind, tool, slug))
        ),
    )


def _breach_alert(
    slug: str = "personal",
    action: str | None = "switch",
    action_slug: str | None = "work",
) -> AlertVM:
    return AlertVM(
        kind="breach",
        tool="codex",
        slug=slug,
        title_text=f"codex/{slug} at 92% of 5h window",
        detail_text="resets in 44m · work is at 42%",
        action=action,
        action_label="Switch to work" if action is not None else "",
        action_slug=action_slug,
    )


def _broken_alert(tool: str = "grok", slug: str = "roy-grok") -> AlertVM:
    return AlertVM(
        kind="broken",
        tool=tool,
        slug=slug,
        title_text=f"{tool}/{slug} auth expired",
        detail_text="health checks paused",
        action="repair",
        action_label="Repair…",
        action_slug=None,
    )


def _vm(
    alerts: tuple[AlertVM, ...] = (),
    healthy_lines: tuple[SummaryLineVM, ...] = (),
) -> AlertsTabVM:
    return AlertsTabVM(
        alerts=alerts,
        healthy_lines=healthy_lines,
        empty_heading="" if alerts else "All good ✓",
        empty_detail="" if alerts else "2 accounts · highest 5h 42%",
    )


def test_banner_per_alert_with_kind_classes_and_texts() -> None:
    vm = _vm(alerts=(_broken_alert(), _breach_alert()))
    tab = AlertsTab(vm, _callbacks([]), gtk_module=_FallbackGtk)

    assert len(tab.banners) == 2
    broken, breach = tab.banners
    assert "alert-banner" in broken.widget.css_classes
    assert "error" in broken.widget.css_classes
    assert broken.title_label.text == "grok/roy-grok auth expired"
    assert broken.detail_label.text == "health checks paused"
    assert "alert-title" in broken.title_label.css_classes
    assert "alert-detail" in broken.detail_label.css_classes
    assert "alert-banner" in breach.widget.css_classes
    assert "warn" in breach.widget.css_classes
    assert breach.title_label.text == "codex/personal at 92% of 5h window"
    assert tab.banners_box.visible is True
    assert tab.empty_box.visible is False


def test_alert_banner_shows_close_control() -> None:
    tab = AlertsTab(_vm(alerts=(_breach_alert(),)), _callbacks([]), gtk_module=_FallbackGtk)

    assert [button.label for button in tab.banners[0].widget.end_children] == [
        "×",
        "Switch to work",
    ]


def test_alert_close_dispatches_its_identity() -> None:
    calls: list[tuple[object, ...]] = []
    tab = AlertsTab(_vm(alerts=(_broken_alert(),)), _callbacks(calls), gtk_module=_FallbackGtk)

    tab.banners[0].close_button.emit("clicked")

    assert calls == [("dismiss", "broken", "grok", "roy-grok")]


def test_switch_button_dispatches_on_set_default_with_target_slug() -> None:
    calls: list[tuple[object, ...]] = []
    tab = AlertsTab(_vm(alerts=(_breach_alert(),)), _callbacks(calls), gtk_module=_FallbackGtk)

    button = tab.banners[0].action_button
    assert button is not None
    assert button.label == "Switch to work"
    assert "suggested-action" in button.css_classes
    button.emit("clicked")

    assert calls == [("set_default", "codex", "work")]


def test_repair_button_dispatches_on_repair_with_alert_slug() -> None:
    calls: list[tuple[object, ...]] = []
    tab = AlertsTab(_vm(alerts=(_broken_alert(),)), _callbacks(calls), gtk_module=_FallbackGtk)

    button = tab.banners[0].action_button
    assert button is not None
    assert button.label == "Repair…"
    button.emit("clicked")

    assert calls == [("repair", "grok", "roy-grok")]


def test_no_action_banner_has_only_close_button() -> None:
    vm = _vm(alerts=(_breach_alert(action=None, action_slug=None),))
    tab = AlertsTab(vm, _callbacks([]), gtk_module=_FallbackGtk)

    row = tab.banners[0]
    assert row.action_button is None
    assert row.widget.children == [row.icon_label, row.text_box, row.close_button]
    assert row.close_button.label == "×"
    assert row.text_box.children == [row.title_label, row.detail_label]


def test_healthy_summary_lines_rendered() -> None:
    lines = (
        SummaryLineVM(text_left="✓ codex/work ★", text_right="5h 42% · 7d 67%"),
        SummaryLineVM(text_left="✓ codex/personal", text_right="5h 12%"),
    )
    tab = AlertsTab(
        _vm(alerts=(_breach_alert(),), healthy_lines=lines),
        _callbacks([]),
        gtk_module=_FallbackGtk,
    )

    assert "healthy-summary" in tab.healthy_box.css_classes
    assert tab.healthy_box.visible is True
    assert len(tab.summary_rows) == 2
    assert tab.summary_rows[0].left_label.text == "✓ codex/work ★"
    assert tab.summary_rows[0].right_label.text == "5h 42% · 7d 67%"
    assert tab.summary_rows[1].left_label.text == "✓ codex/personal"
    assert all("summary-line" in row.widget.css_classes for row in tab.summary_rows)


def test_empty_state_shows_all_good_and_hides_banners() -> None:
    lines = (SummaryLineVM(text_left="✓ codex/work ★", text_right="5h 42%"),)
    tab = AlertsTab(_vm(healthy_lines=lines), _callbacks([]), gtk_module=_FallbackGtk)

    assert "empty-state" in tab.empty_box.css_classes
    assert tab.empty_box.visible is True
    assert tab.empty_heading_label.text == "All good ✓"
    assert tab.empty_detail_label.text == "2 accounts · highest 5h 42%"
    assert tab.banners_box.visible is False
    assert tab.banners == []


def test_update_same_alert_set_mutates_in_place() -> None:
    calls: list[tuple[object, ...]] = []
    tab = AlertsTab(_vm(alerts=(_breach_alert(),)), _callbacks(calls), gtk_module=_FallbackGtk)
    original_row = tab.banners[0]

    updated = AlertVM(
        kind="breach",
        tool="codex",
        slug="personal",
        title_text="codex/personal at 95% of 5h window",
        detail_text="resets in 20m · other is at 10%",
        action="switch",
        action_label="Switch to other",
        action_slug="other",
    )
    tab.update(_vm(alerts=(updated,)))

    assert tab.banners[0] is original_row
    assert original_row.title_label.text == "codex/personal at 95% of 5h window"
    assert original_row.detail_label.text == "resets in 20m · other is at 10%"
    assert original_row.action_button is not None
    assert original_row.action_button.label == "Switch to other"
    original_row.action_button.emit("clicked")
    assert calls == [("set_default", "codex", "other")]


def test_update_changed_alert_set_rebuilds_banners() -> None:
    tab = AlertsTab(_vm(alerts=(_breach_alert(),)), _callbacks([]), gtk_module=_FallbackGtk)
    original_row = tab.banners[0]

    tab.update(_vm(alerts=(_breach_alert(), _broken_alert())))

    assert len(tab.banners) == 2
    assert tab.banners[0] is not original_row
    assert len(tab.banners_box.children) == 2

    tab.update(_vm(healthy_lines=(SummaryLineVM("✓ codex/work", "5h 1%"),)))
    assert tab.banners == []
    assert tab.banners_box.children == []
    assert tab.banners_box.visible is False
    assert tab.empty_box.visible is True


def test_update_transitions_summary_lines_and_empty_state() -> None:
    tab = AlertsTab(_vm(healthy_lines=(SummaryLineVM("✓ a", "5h 1%"),)), _callbacks([]), gtk_module=_FallbackGtk)
    original_row = tab.summary_rows[0]

    tab.update(_vm(healthy_lines=(SummaryLineVM("✓ b", "5h 2%"),)))
    assert tab.summary_rows[0] is original_row
    assert original_row.left_label.text == "✓ b"
    assert original_row.right_label.text == "5h 2%"

    tab.update(_vm(healthy_lines=()))
    assert tab.summary_rows == []
    assert tab.healthy_box.visible is False


def test_constructs_headless_with_fallback_gtk() -> None:
    tab = AlertsTab(_vm(), _callbacks([]), gtk_module=_FallbackGtk)
    assert tab.widget is not None
    assert tab.widget.children == [tab.banners_box, tab.healthy_box, tab.empty_box]
