"""Fetch and normalize Cursor usage data from dashboard APIs."""
from __future__ import annotations

import json
import urllib.error
import urllib.request
from collections import defaultdict
from dataclasses import dataclass, field
from datetime import datetime, timezone

from auth import get_session_cookie, invalidate_session_cache
from errors import ApiHttpError

ORIGIN = "https://cursor.com"
USAGE_SUMMARY_URL = f"{ORIGIN}/api/usage-summary"
PERIOD_USAGE_URL = f"{ORIGIN}/api/dashboard/get-current-period-usage"
FILTERED_EVENTS_URL = f"{ORIGIN}/api/dashboard/get-filtered-usage-events"
MAX_EVENT_PAGES = 50
PAGE_SIZE = 100


@dataclass
class ModelUsage:
    model: str
    requests: int
    tokens: int
    spend_cents: float


@dataclass
class UsageSnapshot:
    included_used: float
    included_limit: float
    included_unit: str  # "dollars" or "raw"
    on_demand_used: float
    on_demand_limit: float | None
    on_demand_enabled: bool
    billing_cycle_end: datetime
    display_message: str
    total_percent_used: float
    auto_percent_used: float
    api_percent_used: float
    models: list[ModelUsage] = field(default_factory=list)


def _request(method: str, url: str, body: dict | None = None) -> dict:
    headers = {
        "Accept": "application/json",
        "Cookie": get_session_cookie(),
        "Origin": ORIGIN,
        "Referer": f"{ORIGIN}/dashboard",
    }
    data = None
    if body is not None:
        headers["Content-Type"] = "application/json"
        data = json.dumps(body).encode()

    req = urllib.request.Request(url, data=data, headers=headers, method=method)
    try:
        with urllib.request.urlopen(req, timeout=60) as response:
            return json.loads(response.read().decode())
    except urllib.error.HTTPError as exc:
        if exc.code in (401, 403):
            invalidate_session_cache()
        detail = exc.read().decode(errors="replace")[:300]
        raise ApiHttpError(exc.code, detail) from exc


def _parse_iso(value: str) -> datetime:
    return datetime.fromisoformat(value.replace("Z", "+00:00"))


def _iso_to_ms(value: str) -> int:
    return int(_parse_iso(value).timestamp() * 1000)


def _aggregate_models(start_ms: int, end_ms: int) -> list[ModelUsage]:
    by_model: dict[str, dict[str, float | int]] = defaultdict(
        lambda: {"requests": 0, "tokens": 0, "spend_cents": 0.0}
    )
    page = 1

    while page <= MAX_EVENT_PAGES:
        payload = {
            "page": page,
            "pageSize": PAGE_SIZE,
            "startDate": str(start_ms),
            "endDate": str(end_ms),
        }
        data = _request("POST", FILTERED_EVENTS_URL, payload)
        events = data.get("usageEventsDisplay", [])
        total = int(data.get("totalUsageEventsCount", 0))

        for event in events:
            model = event.get("model") or "unknown"
            token_usage = event.get("tokenUsage") or {}
            tokens = sum(
                int(token_usage.get(key, 0) or 0)
                for key in (
                    "inputTokens",
                    "outputTokens",
                    "cacheReadTokens",
                    "cacheWriteTokens",
                )
            )
            spend = float(
                token_usage.get("totalCents") or event.get("chargedCents") or 0
            )
            bucket = by_model[model]
            bucket["requests"] = int(bucket["requests"]) + 1
            bucket["tokens"] = int(bucket["tokens"]) + tokens
            bucket["spend_cents"] = float(bucket["spend_cents"]) + spend

        if not events or page * PAGE_SIZE >= total:
            break
        page += 1

    rows = [
        ModelUsage(
            model=model,
            requests=int(stats["requests"]),
            tokens=int(stats["tokens"]),
            spend_cents=float(stats["spend_cents"]),
        )
        for model, stats in by_model.items()
    ]
    rows.sort(key=lambda row: row.spend_cents, reverse=True)
    return rows


def fetch_usage() -> UsageSnapshot:
    summary = _request("GET", USAGE_SUMMARY_URL)
    period = _request("POST", PERIOD_USAGE_URL, {})

    plan = summary.get("individualUsage", {}).get("plan", {})
    on_demand = summary.get("individualUsage", {}).get("onDemand", {})

    included_used = float(plan.get("used", 0))
    included_limit = float(plan.get("limit", 0))
    on_demand_used = float(on_demand.get("used", 0) or 0)
    on_demand_limit = on_demand.get("limit")
    on_demand_limit = float(on_demand_limit) if on_demand_limit is not None else None

    billing_end = _parse_iso(summary["billingCycleEnd"])
    start_ms = _iso_to_ms(summary["billingCycleStart"])
    end_ms = _iso_to_ms(summary["billingCycleEnd"])
    plan_usage = period.get("planUsage", {})
    total_percent_used = float(plan_usage.get("totalPercentUsed", plan.get("totalPercentUsed", 0)))
    auto_percent_used = float(plan_usage.get("autoPercentUsed", plan.get("autoPercentUsed", 0)))
    api_percent_used = float(plan_usage.get("apiPercentUsed", plan.get("apiPercentUsed", 0)))

    return UsageSnapshot(
        included_used=included_used,
        included_limit=included_limit,
        included_unit="dollars",
        on_demand_used=on_demand_used,
        on_demand_limit=on_demand_limit,
        on_demand_enabled=bool(on_demand.get("enabled")),
        billing_cycle_end=billing_end,
        display_message=period.get("displayMessage", ""),
        total_percent_used=total_percent_used,
        auto_percent_used=auto_percent_used,
        api_percent_used=api_percent_used,
        models=_aggregate_models(start_ms, end_ms),
    )


def format_tokens(value: int) -> str:
    if value >= 1_000_000_000:
        return f"{value / 1_000_000_000:.1f}B"
    if value >= 1_000_000:
        return f"{value / 1_000_000:.1f}M"
    if value >= 1_000:
        return f"{value / 1_000:.1f}K"
    return str(value)


def format_dollars(cents: float) -> str:
    return f"${cents / 100:.2f}"


def _as_local(dt: datetime) -> datetime:
    """UTC-aware → lokální čas; naive → už je lokální (datetime.now())."""
    if dt.tzinfo is not None:
        return dt.astimezone()
    return dt


def format_billing_reset(end: datetime) -> str:
    """Lokální datum a čas konce billing cyklu."""
    return _as_local(end).strftime("%b %d, %Y %H:%M")


def format_last_updated(when: datetime | None = None) -> str:
    return format_billing_reset(when or datetime.now())


def format_billing_reset_short(end: datetime) -> str:
    """Kompaktní datum pro tray tooltip (Windows max. 128 znaků)."""
    return _as_local(end).strftime("%b %d %H:%M")


TRAY_TOOLTIP_MAX_LEN = 128


def clamp_tray_tooltip(text: str, max_len: int = TRAY_TOOLTIP_MAX_LEN) -> str:
    if len(text) <= max_len:
        return text
    return text[: max_len - 1] + "…"


def days_until_reset(end: datetime) -> int:
    now = datetime.now(timezone.utc)
    end_utc = end if end.tzinfo else end.replace(tzinfo=timezone.utc)
    delta = end_utc.date() - now.date()
    return max(delta.days, 0)


def _included_tooltip_line(snapshot: UsageSnapshot) -> str:
    if snapshot.included_unit == "dollars":
        return (
            f"Included {format_dollars(snapshot.included_used)} / "
            f"{format_dollars(snapshot.included_limit)}"
        )
    return f"Included {int(snapshot.included_used)} / {int(snapshot.included_limit)}"


def _ondemand_tooltip_line(snapshot: UsageSnapshot) -> str:
    used = format_dollars(snapshot.on_demand_used)
    if snapshot.on_demand_enabled and snapshot.on_demand_limit:
        return f"On-demand {used} / {format_dollars(snapshot.on_demand_limit)}"
    return f"On-demand {used} / —"


def format_reset_line(days: int, end: datetime) -> str:
    day_word = "day" if days == 1 else "days"
    return f"Resets in {days} {day_word} on {format_billing_reset(end)}"


def format_app_title(plan: str | None = None) -> str:
    if plan:
        return f"Cursor {plan} Usage"
    return "Cursor Usage"


def format_tray_tooltip(snapshot: UsageSnapshot, plan: str | None = None) -> str:
    days = days_until_reset(snapshot.billing_cycle_end)
    header = f"{format_app_title(plan)}:"
    text = "\n".join(
        [
            header,
            f"Total {snapshot.total_percent_used:.1f}%",
            f"Auto {snapshot.auto_percent_used:.1f}%",
            f"API {snapshot.api_percent_used:.1f}%",
            _included_tooltip_line(snapshot),
            _ondemand_tooltip_line(snapshot),
            f"Resets in {days}d on {format_billing_reset_short(snapshot.billing_cycle_end)}",
        ]
    )
    return clamp_tray_tooltip(text)
