"""
grantflow/api/tenant_store.py
-----------------------------
In-process tenant registry with optional Redis write-through.

Tenants registered here define display name, allowed donors, and daily quotas.
Quotas are advisory read signals — they do not hard-block requests, but callers
can check them before accepting work.

Configure Redis with GRANTFLOW_REDIS_URL for durability across restarts.
Without Redis, the registry lives in process memory only.
"""

from __future__ import annotations

import json
import logging
import threading
from datetime import datetime, timezone
from typing import Any, Optional

_log = logging.getLogger(__name__)
_lock = threading.Lock()

# In-process registry: tenant_id → record dict
_TENANT_REGISTRY: dict[str, dict[str, Any]] = {}

_REDIS_KEY_PREFIX = "grantflow:tenant:"


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _utcnow_iso() -> str:
    return datetime.now(timezone.utc).isoformat(timespec="seconds")


def _redis_tenant_key(tenant_id: str) -> str:
    return f"{_REDIS_KEY_PREFIX}{tenant_id}"


def _try_redis_get(tenant_id: str) -> Optional[dict[str, Any]]:
    try:
        from grantflow.api.redis_store import _get_redis

        r = _get_redis()
        if r is None:
            return None
        raw = r.get(_redis_tenant_key(tenant_id))
        if raw:
            return json.loads(raw)
    except Exception as exc:
        _log.warning("tenant_store redis get failed for %s: %s", tenant_id, exc)
    return None


def _try_redis_set(tenant_id: str, record: dict[str, Any]) -> None:
    try:
        from grantflow.api.redis_store import _get_redis

        r = _get_redis()
        if r is None:
            return
        r.set(_redis_tenant_key(tenant_id), json.dumps(record, ensure_ascii=True))
    except Exception as exc:
        _log.warning("tenant_store redis set failed for %s: %s", tenant_id, exc)


def _try_redis_list() -> list[dict[str, Any]]:
    try:
        from grantflow.api.redis_store import _get_redis

        r = _get_redis()
        if r is None:
            return []
        keys = list(r.keys(f"{_REDIS_KEY_PREFIX}*"))
        if not keys:
            return []
        records = []
        for key in keys:
            raw = r.get(key)
            if raw:
                try:
                    records.append(json.loads(raw))
                except Exception:
                    pass
        return records
    except Exception as exc:
        _log.warning("tenant_store redis list failed: %s", exc)
    return []


# ---------------------------------------------------------------------------
# Public API
# ---------------------------------------------------------------------------


def register_tenant(
    tenant_id: str,
    *,
    display_name: Optional[str] = None,
    allowed_donors: Optional[list[str]] = None,
    quota_jobs_per_day: int = 0,
    quota_ingest_per_day: int = 0,
) -> dict[str, Any]:
    """Create or update a tenant record.

    Args:
        tenant_id: Normalized slug (lowercase, no spaces).
        display_name: Human-readable label.
        allowed_donors: List of allowed donor_id values. Empty list = all allowed.
        quota_jobs_per_day: Max generate calls per UTC day. 0 = unlimited.
        quota_ingest_per_day: Max ingest calls per UTC day. 0 = unlimited.

    Returns:
        The stored tenant record dict.
    """
    normalized = str(tenant_id or "").strip().lower().replace(" ", "_")
    if not normalized:
        raise ValueError("tenant_id must not be empty")

    with _lock:
        existing = _TENANT_REGISTRY.get(normalized)
        now = _utcnow_iso()
        record: dict[str, Any] = {
            "tenant_id": normalized,
            "display_name": str(display_name or normalized),
            "allowed_donors": sorted(set(allowed_donors)) if allowed_donors else [],
            "quota_jobs_per_day": max(0, int(quota_jobs_per_day)),
            "quota_ingest_per_day": max(0, int(quota_ingest_per_day)),
            "created_at": existing.get("created_at", now) if existing else now,
            "updated_at": now,
        }
        _TENANT_REGISTRY[normalized] = record

    _try_redis_set(normalized, record)
    return dict(record)


def get_tenant(tenant_id: str) -> Optional[dict[str, Any]]:
    """Return tenant config, or None if not found.

    Checks Redis on cache miss so tenants registered before a restart
    are visible without re-provisioning.
    """
    normalized = str(tenant_id or "").strip().lower()
    if not normalized:
        return None

    with _lock:
        if normalized in _TENANT_REGISTRY:
            return dict(_TENANT_REGISTRY[normalized])

    record = _try_redis_get(normalized)
    if record:
        with _lock:
            _TENANT_REGISTRY[normalized] = record
        return dict(record)

    return None


def list_tenants() -> list[dict[str, Any]]:
    """Return all known tenants, newest-first.

    Merges in-process registry with Redis to surface tenants created in
    other processes or before a restart.
    """
    with _lock:
        in_process = {k: dict(v) for k, v in _TENANT_REGISTRY.items()}

    for record in _try_redis_list():
        tid = str(record.get("tenant_id") or "").strip()
        if tid and tid not in in_process:
            in_process[tid] = record
            with _lock:
                _TENANT_REGISTRY[tid] = record

    return sorted(in_process.values(), key=lambda r: str(r.get("updated_at") or ""), reverse=True)


def _clear_registry_for_tests() -> None:
    """Reset in-process registry. Only used in tests."""
    with _lock:
        _TENANT_REGISTRY.clear()
