# retoor <retoor@molodetz.nl>

from __future__ import annotations

from datetime import datetime, timedelta, timezone
from typing import Optional

from devplacepy.cache import TTLCache

MAX_WINDOW_HOURS = 2160
RETENTION_DAYS = 90

_stats_cache = TTLCache(ttl=60, max_size=200)

VALID_TABS = (
    "overview",
    "visitors",
    "members",
    "content",
    "engagement",
    "social",
    "ai",
    "devii",
    "services",
    "containers",
    "game",
    "awards",
    "moderation",
    "tools",
    "storage",
)


def now_utc() -> datetime:
    return datetime.now(timezone.utc)


def iso(moment: datetime) -> str:
    return moment.isoformat(timespec="seconds")


def hour_bucket(moment: datetime) -> str:
    return moment.replace(minute=0, second=0, microsecond=0).isoformat(timespec="seconds")


def window_config(hours: int) -> dict:
    now = now_utc()
    if hours <= 0:
        return {
            "hours": 0,
            "start": None,
            "compare_start": None,
            "end": iso(now),
            "granularity": "weekly",
        }
    bounded = max(1, min(int(hours), MAX_WINDOW_HOURS))
    start = now - timedelta(hours=bounded)
    compare_start = start - timedelta(hours=bounded)
    if bounded <= 48:
        granularity = "hourly"
    elif bounded <= 720:
        granularity = "daily"
    else:
        granularity = "daily"
    return {
        "hours": bounded,
        "start": iso(start),
        "compare_start": iso(compare_start),
        "end": iso(now),
        "granularity": granularity,
    }


def bucket_expr(granularity: str, column: str = "created_at") -> str:
    if granularity == "hourly":
        return f"strftime('%Y-%m-%dT%H:00:00', {column})"
    if granularity == "weekly":
        return f"strftime('%Y-W%W', {column})"
    return f"date({column})"


def delta_pair(current: float, previous: float) -> dict:
    if previous == 0:
        pct = 100.0 if current > 0 else 0.0
    else:
        pct = round((current - previous) / previous * 100, 1)
    if pct > 0:
        direction = "up"
    elif pct < 0:
        direction = "down"
    else:
        direction = "flat"
    return {"delta": pct, "direction": direction}


def metric_card(
    key: str,
    label: str,
    value,
    *,
    current: Optional[float] = None,
    previous: Optional[float] = None,
    format_kind: str = "int",
) -> dict:
    card = {"key": key, "label": label, "value": value, "format": format_kind}
    if current is not None and previous is not None:
        card.update(delta_pair(current, previous))
    return card


def fill_series(
    points: dict[str, float],
    start: Optional[str],
    end: str,
    granularity: str,
) -> list[dict]:
    if not start:
        ordered = sorted(points.items())
        return [{"t": bucket, "v": points[bucket]} for bucket, _ in ordered]
    try:
        cursor = datetime.fromisoformat(start)
        end_dt = datetime.fromisoformat(end)
    except (TypeError, ValueError):
        ordered = sorted(points.items())
        return [{"t": bucket, "v": points[bucket]} for bucket, _ in ordered]
    if cursor.tzinfo is None:
        cursor = cursor.replace(tzinfo=timezone.utc)
    if end_dt.tzinfo is None:
        end_dt = end_dt.replace(tzinfo=timezone.utc)
    out: list[dict] = []
    if granularity == "hourly":
        step = timedelta(hours=1)
        fmt = lambda moment: hour_bucket(moment)
    elif granularity == "weekly":
        step = timedelta(days=7)
        fmt = lambda moment: moment.strftime("%Y-W%W")
    else:
        step = timedelta(days=1)
        fmt = lambda moment: moment.date().isoformat()
    while cursor <= end_dt:
        key = fmt(cursor)
        out.append({"t": key, "v": points.get(key, 0)})
        cursor += step
    return out


def cache_get(key: str):
    return _stats_cache.get(key)


def cache_set(key: str, payload: dict) -> None:
    _stats_cache.set(key, payload)


def series_payload(
    key: str,
    label: str,
    points: dict[str, float],
    window: dict,
) -> dict:
    return {
        "key": key,
        "label": label,
        "points": fill_series(points, window["start"], window["end"], window["granularity"]),
    }


def table_payload(key: str, title: str, columns: list[str], rows: list[list]) -> dict:
    return {"key": key, "title": title, "columns": columns, "rows": rows}