from __future__ import annotations

import sys
from dataclasses import replace
from pathlib import Path
from typing import Any

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

from ui.dashboard import Dashboard, _FallbackGtk
from ui.view_model import (
    AlertsTabVM,
    AlertVM,
    DashboardCallbacks,
    DashboardVM,
    ProviderTabVM,
    TabVM,
)


class _StubWidget:
    def __init__(self) -> None:
        self.visible = True

    def set_visible(self, visible: bool) -> None:
        self.visible = visible


class _StubTab:
    def __init__(self, vm: Any, callbacks: DashboardCallbacks) -> None:
        self.vm = vm
        self.callbacks = callbacks
        self.widget = _StubWidget()
        self.updates: list[Any] = []

    def update(self, vm: Any) -> None:
        self.vm = vm
        self.updates.append(vm)


class _StubFooter:
    def __init__(
        self,
        add_label: str,
        add_tools: tuple[tuple[str, str], ...],
        refresh_text: str,
        callbacks: DashboardCallbacks,
    ) -> None:
        self.add_label = add_label
        self.add_tools = add_tools
        self.refresh_text = refresh_text
        self.callbacks = callbacks
        self.widget = _StubWidget()
        self.updates: list[tuple[str, tuple[tuple[str, str], ...], str]] = []
        self.busy_tools: frozenset[str] = frozenset()

    def update(
        self,
        add_label: str,
        add_tools: tuple[tuple[str, str], ...],
        refresh_text: str,
        busy_tools: frozenset[str] = frozenset(),
    ) -> None:
        self.add_label = add_label
        self.add_tools = add_tools
        self.refresh_text = refresh_text
        self.busy_tools = busy_tools
        self.updates.append((add_label, add_tools, refresh_text))


def _callbacks(tab_changes: list[str] | None = None) -> DashboardCallbacks:
    changes = tab_changes if tab_changes is not None else []
    return DashboardCallbacks(
        on_set_default=lambda _tool, _slug: None,
        on_reload=lambda _tool, _slug: None,
        on_repair=lambda _tool, _slug: None,
        on_rename=lambda _tool, _slug: None,
        on_remove=lambda _tool, _slug: None,
        on_add=lambda _tool: None,
        on_open_settings=lambda: None,
        on_set_limits=lambda _tool, _slug: None,
        on_tab_changed=changes.append,
    )


_LABELS = {"codex": "Codex", "claude": "Claude Code", "grok": "Grok"}


def _vm(
    active_tab: str = "codex",
    tools: tuple[str, ...] = ("codex", "claude"),
    refresh_text: str = "⟳ 5s ago",
    alerts: tuple[AlertVM, ...] = (),
) -> DashboardVM:
    tabs = (
        TabVM(id="alerts", label="⚠ Alerts", badge_count=len(alerts), badge_level=""),
        *(
            TabVM(id=tool, label=_LABELS[tool], badge_count=0, badge_level="")
            for tool in tools
        ),
    )
    providers = tuple(
        ProviderTabVM(
            tool=tool,
            accounts=(),
            add_label=f"＋ Add {_LABELS[tool]} account",
        )
        for tool in tools
    )
    return DashboardVM(
        tabs=tabs,
        alerts=AlertsTabVM(
            alerts=alerts,
            healthy_lines=(),
            empty_heading="" if alerts else "All good ✓",
            empty_detail="",
        ),
        providers=providers,
        active_tab=active_tab,
        add_tools=tuple((tool, _LABELS[tool]) for tool in tools),
        refresh_text=refresh_text,
    )


def _dashboard(tab_changes: list[str] | None = None) -> Dashboard:
    return Dashboard(
        _callbacks(tab_changes),
        gtk_module=_FallbackGtk,
        alerts_tab_factory=_StubTab,
        provider_tab_factory=_StubTab,
        footer_factory=_StubFooter,
    )


def test_update_composes_tab_bar_pages_and_footer() -> None:
    changes: list[str] = []
    dashboard = _dashboard(changes)
    vm = _vm()

    dashboard.update(vm)

    assert "dashboard" in dashboard.widget.css_classes
    assert dashboard.tab_bar is not None
    assert list(dashboard.tab_bar.buttons) == ["alerts", "codex", "claude"]
    assert dashboard.tab_bar.active == "codex"
    assert dashboard.alerts_tab is not None
    assert dashboard.alerts_tab.vm is vm.alerts
    assert set(dashboard.provider_tabs) == {"codex", "claude"}
    assert dashboard.provider_tabs["codex"].vm is vm.providers[0]

    assert dashboard.footer is not None
    assert dashboard.footer.add_label == "＋ Add Codex account"
    assert dashboard.footer.add_tools == (("codex", "Codex"),)
    assert dashboard.footer.refresh_text == "⟳ 5s ago"

    assert dashboard.provider_tabs["codex"].widget.visible is True
    assert dashboard.provider_tabs["claude"].widget.visible is False
    assert dashboard.alerts_tab.widget.visible is False
    assert changes == []


def test_update_same_shape_swaps_nothing_structurally() -> None:
    changes: list[str] = []
    dashboard = _dashboard(changes)
    dashboard.update(_vm())

    tab_bar = dashboard.tab_bar
    alerts_tab = dashboard.alerts_tab
    codex_tab = dashboard.provider_tabs["codex"]
    claude_tab = dashboard.provider_tabs["claude"]
    footer = dashboard.footer
    assert tab_bar is not None
    buttons = dict(tab_bar.buttons)

    second = _vm(refresh_text="⟳ 42s ago")
    dashboard.update(second)

    assert dashboard.tab_bar is tab_bar
    assert dashboard.alerts_tab is alerts_tab
    assert dashboard.provider_tabs["codex"] is codex_tab
    assert dashboard.provider_tabs["claude"] is claude_tab
    assert dashboard.footer is footer
    for tab_id, button in buttons.items():
        assert tab_bar.buttons[tab_id] is button
    assert alerts_tab is not None
    assert alerts_tab.updates == [second.alerts]
    assert codex_tab.updates == [second.providers[0]]
    assert footer is not None
    assert footer.updates == [("＋ Add Codex account", (("codex", "Codex"),), "⟳ 42s ago")]
    assert changes == []


def test_update_never_switches_active_tab() -> None:
    changes: list[str] = []
    dashboard = _dashboard(changes)
    dashboard.update(_vm(active_tab="codex"))

    alert = AlertVM(
        kind="broken",
        tool="claude",
        slug="work",
        title_text="claude/work auth expired",
        detail_text="health checks paused",
        action="repair",
        action_label="Repair…",
        action_slug=None,
    )
    dashboard.update(_vm(active_tab="alerts", alerts=(alert,)))

    assert dashboard.tab_bar is not None
    assert dashboard.tab_bar.active == "codex"
    assert dashboard.provider_tabs["codex"].widget.visible is True
    assert dashboard.alerts_tab is not None
    assert dashboard.alerts_tab.widget.visible is False
    assert changes == []


def test_show_tab_switches_page_updates_footer_and_fires_on_tab_changed() -> None:
    changes: list[str] = []
    dashboard = _dashboard(changes)
    dashboard.update(_vm())

    dashboard.show_tab("claude")

    assert changes == ["claude"]
    assert dashboard.tab_bar is not None
    assert dashboard.tab_bar.active == "claude"
    assert dashboard.provider_tabs["claude"].widget.visible is True
    assert dashboard.provider_tabs["codex"].widget.visible is False
    assert dashboard.footer is not None
    assert dashboard.footer.add_label == "＋ Add Claude Code account"
    assert dashboard.footer.add_tools == (("claude", "Claude Code"),)


def test_footer_add_label_follows_tab_to_alerts() -> None:
    changes: list[str] = []
    dashboard = _dashboard(changes)
    vm = _vm()
    dashboard.update(vm)

    dashboard.show_tab("alerts")

    assert changes == ["alerts"]
    assert dashboard.footer is not None
    assert dashboard.footer.add_label == "＋ Add account"
    assert dashboard.footer.add_tools == vm.add_tools
    assert dashboard.alerts_tab is not None
    assert dashboard.alerts_tab.widget.visible is True


def test_tab_bar_click_routes_through_show_tab() -> None:
    changes: list[str] = []
    dashboard = _dashboard(changes)
    dashboard.update(_vm())

    assert dashboard.tab_bar is not None
    dashboard.tab_bar.buttons["claude"].emit("clicked")

    assert changes == ["claude"]
    assert dashboard.provider_tabs["claude"].widget.visible is True
    assert dashboard.footer is not None
    assert dashboard.footer.add_label == "＋ Add Claude Code account"


def test_show_tab_ignores_unknown_tab_id() -> None:
    changes: list[str] = []
    dashboard = _dashboard(changes)
    dashboard.update(_vm())

    dashboard.show_tab("grok")

    assert changes == []
    assert dashboard.tab_bar is not None
    assert dashboard.tab_bar.active == "codex"


def test_update_adds_and_removes_provider_pages() -> None:
    changes: list[str] = []
    dashboard = _dashboard(changes)
    dashboard.update(_vm(tools=("codex", "claude")))
    codex_tab = dashboard.provider_tabs["codex"]

    dashboard.update(_vm(tools=("codex", "grok")))

    assert set(dashboard.provider_tabs) == {"codex", "grok"}
    assert dashboard.provider_tabs["codex"] is codex_tab
    assert dashboard.tab_bar is not None
    assert dashboard.tab_bar.active == "codex"
    assert dashboard.provider_tabs["grok"].widget.visible is False
    assert changes == []


def test_update_keeps_active_tab_even_when_active_provider_removed() -> None:
    changes: list[str] = []
    dashboard = _dashboard(changes)
    dashboard.update(_vm(tools=("codex", "claude"), active_tab="claude"))

    dashboard.update(_vm(tools=("codex",)))

    assert dashboard.tab_bar is not None
    assert dashboard.tab_bar.active == "claude"
    assert changes == []


def test_footer_busy_add_tools_flow_through_updates() -> None:
    dashboard = _dashboard()
    dashboard.update(_vm())
    assert dashboard.footer is not None
    assert dashboard.footer.busy_tools == frozenset()

    dashboard.update(replace(_vm(), busy_add_tools=frozenset({"codex"})))
    assert dashboard.footer.busy_tools == frozenset({"codex"})

    dashboard.update(_vm())
    assert dashboard.footer.busy_tools == frozenset()
