from __future__ import annotations

from dataclasses import dataclass
from enum import Enum

from account_lock import AccountLockError
from account_registry import Account, AccountRegistry, AccountRegistryKind
from health_client import AccountSnapshot, HealthStatus
from gateway_account_manager import GatewayAccountState, gateway_account_state, gateway_state_label

LIMIT_ROW_FRESH_SECONDS = 10 * 60


class TrayMenuItemKind(str, Enum):
    ACTION = "action"
    RADIO = "radio"
    SEPARATOR = "separator"
    SUBMENU = "submenu"


@dataclass(frozen=True)
class TrayAccountItem:
    key: str
    label: str
    active: bool
    provider: str
    locked: bool = False


@dataclass(frozen=True)
class TrayMenuItem:
    kind: TrayMenuItemKind
    label: str = ""
    active: bool = False
    sensitive: bool = True
    account_key: str | None = None
    action: str | None = None
    children: tuple["TrayMenuItem", ...] = ()


@dataclass(frozen=True)
class TrayModel:
    title: str = ""
    menu_items: tuple[TrayMenuItem, ...] = ()
    account_items: tuple[TrayAccountItem, ...] = ()


def build_tray_model(
    registry: AccountRegistry,
    snapshots: dict[str, AccountSnapshot],
    claude_registry: AccountRegistry | None = None,
    now: float | None = None,
) -> TrayModel:
    account_items: list[TrayAccountItem] = []
    if claude_registry is None:
        default_account = _default_account(registry)
        title = _title_part(
            "Codex",
            default_account,
            locked=_account_is_locked(registry, default_account),
        )
        menu_items = _single_registry_menu(registry, snapshots, account_items, now=now)
    else:
        codex_default = _default_account(registry)
        claude_default = _default_account(claude_registry)
        codex_title = _title_part(
            "Codex",
            codex_default,
            locked=_account_is_locked(registry, codex_default),
        )
        claude_title = _title_part(
            "Claude",
            claude_default,
            locked=_account_is_locked(claude_registry, claude_default),
        )
        title = f"{codex_title} · {claude_title}"
        menu_items = _dual_registry_menu(registry, claude_registry, snapshots, account_items, now=now)
    return TrayModel(title=title, menu_items=tuple(menu_items), account_items=tuple(account_items))


def format_title_part(
    label: str,
    account: Account | None,
    *,
    locked: bool = False,
) -> str:
    return _title_part(label, account, locked=locked)


def format_account_label(
    account: Account,
    snapshot: AccountSnapshot | None = None,
    *,
    show_health: bool = False,
    now: float | None = None,
    locked: bool | None = None,
) -> str:
    plan = account.plan if account.plan else "unknown"
    summary_parts = [account.alias, plan]
    if account.tool == AccountRegistryKind.CLAUDE.value and account.email:
        summary_parts.append(account.email)
    if gateway_account_state(account) is not GatewayAccountState.NATIVE:
        summary_parts.append(gateway_state_label(account))
    summary = " · ".join(summary_parts)
    if locked is not None:
        summary = f"{'🔒' if locked else '🔓'} {summary}"
    if snapshot is None:
        return summary
    if snapshot.status == HealthStatus.BROKEN:
        return f"{summary} · ⚠ needs re-login"
    rows = format_limit_rows(snapshot, now=now, tool=account.tool)
    if rows:
        return summary + _age_suffix(snapshot.checked_at, now) + "\n" + "\n".join(rows)
    return summary


def _age_suffix(checked_at: float | None, now: float | None) -> str:
    if checked_at is None or now is None:
        return ""
    age = _duration_text(int(now - checked_at))
    if age is None or now - checked_at <= LIMIT_ROW_FRESH_SECONDS:
        return ""
    return f" · {age} old"


def progress_row(remaining_pct: int) -> str:
    bounded_pct = max(0, min(remaining_pct, 100))
    filled = round((bounded_pct / 100) * 20)
    empty = 20 - filled
    return f"[{'#' * filled}{'-' * empty}] {bounded_pct}%"


def format_limit_rows(
    snapshot: AccountSnapshot,
    *,
    now: float | None = None,
    tool: str | None = None,
) -> tuple[str, ...] | None:
    if snapshot.status == HealthStatus.BROKEN:
        return None
    rows: list[str] = []
    if snapshot.primary_used_pct is not None:
        rows.append(_limit_row("5h", snapshot.primary_used_pct, snapshot.primary_reset_at, now))
    if snapshot.secondary_used_pct is not None:
        # Grok uses billing period (monthly) on the secondary slot only.
        secondary_label = "mo" if tool == "grok" else "7d"
        rows.append(
            _limit_row(
                secondary_label,
                snapshot.secondary_used_pct,
                snapshot.secondary_reset_at,
                now,
            )
        )
    return tuple(rows) if rows else None


def _limit_row(label: str, used_pct: int, reset_at: float | None, now: float | None) -> str:
    remaining_pct = _remaining_percent(used_pct)
    row = f"{label} {progress_row(remaining_pct)} left"
    reset_delta = _reset_delta(reset_at, now)
    if reset_delta is None:
        return row
    return f"{row} (resets in {reset_delta})"


def _remaining_percent(used_pct: int) -> int:
    bounded_used = max(0, min(used_pct, 100))
    return 100 - bounded_used


def _reset_delta(reset_at: float | None, now: float | None) -> str | None:
    if reset_at is None or now is None:
        return None
    return _duration_text(int(reset_at - now))


def _duration_text(seconds: int) -> str | None:
    if seconds <= 0:
        return None
    if seconds < 3600:
        minutes = max(1, (seconds + 59) // 60)
        return f"{minutes}m"
    if seconds < 86400:
        hours = seconds // 3600
        minutes = (seconds % 3600) // 60
        if minutes == 0:
            return f"{hours}h"
        return f"{hours}h{minutes}m"
    days = seconds // 86400
    hours = (seconds % 86400) // 3600
    if hours == 0:
        return f"{days}d"
    return f"{days}d{hours}h"


def _single_registry_menu(
    registry: AccountRegistry,
    snapshots: dict[str, AccountSnapshot],
    account_items: list[TrayAccountItem],
    *,
    now: float | None = None,
) -> list[TrayMenuItem]:
    items = _account_menu_items(registry, snapshots, account_items, show_health=True, now=now)
    items.append(_separator())
    items.append(_action_item("Open dashboard", action="open_dashboard"))
    items.append(_separator())
    items.append(_manage_accounts_item(registry))
    items.append(_separator())
    items.append(_action_item("Quit", action="quit"))
    return items


def _dual_registry_menu(
    codex_registry: AccountRegistry,
    claude_registry: AccountRegistry,
    snapshots: dict[str, AccountSnapshot],
    account_items: list[TrayAccountItem],
    *,
    now: float | None = None,
) -> list[TrayMenuItem]:
    codex_children = _account_menu_items(
        codex_registry,
        snapshots,
        account_items,
        show_health=True,
        now=now,
    )
    codex_children.append(_separator())
    codex_children.append(_manage_accounts_item(codex_registry))
    claude_children = _account_menu_items(
        claude_registry,
        snapshots,
        account_items,
        show_health=True,
        now=now,
    )
    return [
        _submenu_item("Codex", codex_children),
        _submenu_item("Claude Code", claude_children),
        _separator(),
        _action_item("Quit", action="quit"),
    ]


def _account_menu_items(
    registry: AccountRegistry,
    snapshots: dict[str, AccountSnapshot],
    account_items: list[TrayAccountItem],
    *,
    show_health: bool,
    now: float | None = None,
) -> list[TrayMenuItem]:
    items: list[TrayMenuItem] = []
    default_slug = registry.default_slug()
    for account in registry.list():
        snapshot = snapshots.get(account.tray_key) if show_health else None
        locked = _account_is_locked(registry, account)
        label = format_account_label(
            account,
            snapshot,
            show_health=show_health,
            now=now,
            locked=locked,
        )
        active = account.slug == default_slug
        account_items.append(
            TrayAccountItem(
                key=account.tray_key,
                label=label,
                active=active,
                provider=_provider_label(registry),
                locked=locked,
            )
        )
        items.append(
            TrayMenuItem(
                kind=TrayMenuItemKind.RADIO,
                label=label,
                active=active,
                sensitive=not locked,
                account_key=account.tray_key,
            )
        )
    return items


def _manage_accounts_item(registry: AccountRegistry) -> TrayMenuItem:
    children: list[TrayMenuItem] = [_action_item("Add account…", action="add_account")]
    accounts = registry.list()
    if accounts:
        children.append(_separator())
    for account in accounts:
        locked = _account_is_locked(registry, account)
        children.append(
            _action_item(
                f"{'Unlock' if locked else 'Lock'} {account.alias}",
                action="toggle_account_lock",
                account_key=account.tray_key,
            )
        )
        children.append(
            _action_item(
                f"Refresh login for {account.alias}…",
                action="repair_account",
                account_key=account.tray_key,
                sensitive=not locked,
            )
        )
        children.append(
            _action_item(
                f"Rename {account.alias}…",
                action="rename_account",
                account_key=account.tray_key,
            )
        )
        children.append(
            _action_item(
                f"Remove {account.alias}…",
                action="remove_account",
                account_key=account.tray_key,
            )
        )
    return _submenu_item("Manage accounts", children)


def _submenu_item(label: str, children: list[TrayMenuItem]) -> TrayMenuItem:
    return TrayMenuItem(
        kind=TrayMenuItemKind.SUBMENU,
        label=label,
        children=tuple(children),
    )


def _action_item(
    label: str,
    *,
    action: str,
    account_key: str | None = None,
    sensitive: bool = True,
) -> TrayMenuItem:
    return TrayMenuItem(
        kind=TrayMenuItemKind.ACTION,
        label=label,
        action=action,
        account_key=account_key,
        sensitive=sensitive,
    )


def _separator() -> TrayMenuItem:
    return TrayMenuItem(kind=TrayMenuItemKind.SEPARATOR)


def _default_account(registry: AccountRegistry) -> Account | None:
    default_slug = registry.default_slug()
    if default_slug is None:
        return None
    for account in registry.list():
        if account.slug == default_slug:
            return account
    return None


def _title_part(label: str, account: Account | None, *, locked: bool = False) -> str:
    if account is None or not account.plan:
        return f"{label}: unknown account"
    lock_prefix = "🔒 " if locked else ""
    return f"{lock_prefix}{label}: {account.alias} ({account.plan})"


def _account_is_locked(registry: AccountRegistry, account: Account | None) -> bool:
    if account is None:
        return False
    checker = getattr(registry, "is_locked", None)
    if not callable(checker):
        return False
    try:
        return bool(checker(account.slug))
    except (AccountLockError, OSError):
        # Invalid or unreadable policy state must never make an account selectable.
        return True


def _provider_label(registry: AccountRegistry) -> str:
    kind = getattr(registry, "kind", None)
    if kind == AccountRegistryKind.CLAUDE:
        return "Claude Code"
    return "Codex"
