"""Per-mailbox authenticated send quotas.

Account fields of 0 inherit the global service limits from env
(LIMRISTEM_MAIL_SEND_RATE_*). Resolved limits are published to Redis for the
Rspamd Lua enforcer and to a map file under /var/lib/limristem-mail/rspamd.
"""

from __future__ import annotations

import json
import logging
import os
from pathlib import Path
from typing import Any, Iterable

from .cache import get_cache, safe_cache_delete, safe_cache_set

logger = logging.getLogger(__name__)

REDIS_CFG_PREFIX = "limristem:sendlim:cfg:"
REDIS_GLOBAL_KEY = "limristem:sendlim:cfg:__global__"
MAP_FILE = Path("/var/lib/limristem-mail/rspamd/user_send_limits.map")

LIMIT_FIELDS = (
    "send_rate_burst",
    "send_rate_per_minute",
    "send_rate_per_hour",
    "send_rate_per_day",
    "send_rate_per_week",
    "send_rate_per_month",
)

GLOBAL_ENV_MAP = {
    "send_rate_burst": ("LIMRISTEM_MAIL_SEND_RATE_BURST", "20"),
    "send_rate_per_minute": ("LIMRISTEM_MAIL_SEND_RATE_PER_MINUTE", "10"),
    "send_rate_per_hour": ("LIMRISTEM_MAIL_SEND_RATE_PER_HOUR", "100"),
    "send_rate_per_day": ("LIMRISTEM_MAIL_SEND_RATE_PER_DAY", "500"),
    "send_rate_per_week": ("LIMRISTEM_MAIL_SEND_RATE_PER_WEEK", "2000"),
    "send_rate_per_month": ("LIMRISTEM_MAIL_SEND_RATE_PER_MONTH", "5000"),
}


def _env_int(name: str, default: str) -> int:
    raw = os.getenv(name, default)
    try:
        return max(0, int(str(raw).strip() or default))
    except ValueError:
        return int(default)


def global_send_limits() -> dict[str, int]:
    return {field: _env_int(env_name, default) for field, (env_name, default) in GLOBAL_ENV_MAP.items()}


def account_override_limits(account: object) -> dict[str, int]:
    result: dict[str, int] = {}
    for field in LIMIT_FIELDS:
        try:
            result[field] = max(0, int(getattr(account, field, 0) or 0))
        except (TypeError, ValueError):
            result[field] = 0
    return result


def resolve_send_limits(account: object | None = None) -> dict[str, int]:
    """Merge per-account overrides (non-zero) over global defaults."""
    resolved = global_send_limits()
    if account is None:
        return resolved
    overrides = account_override_limits(account)
    for field, value in overrides.items():
        if value > 0:
            resolved[field] = value
    return resolved


def effective_limits_payload(account: object | None = None) -> dict[str, int]:
    """Return both raw overrides and effective resolved limits for API output."""
    overrides = account_override_limits(account) if account is not None else {f: 0 for f in LIMIT_FIELDS}
    resolved = resolve_send_limits(account)
    payload = dict(overrides)
    for field, value in resolved.items():
        payload[f"effective_{field}"] = value
    return payload


def account_email(account: object) -> str:
    username = getattr(account, "username", None)
    if username:
        return str(username).strip().lower()
    domain = getattr(account, "domain", None)
    domain_name = getattr(domain, "name", None) if domain is not None else None
    local_part = getattr(account, "local_part", None)
    if domain_name and local_part:
        return f"{local_part}@{domain_name}".lower()
    return ""


def _cfg_key(email: str) -> str:
    return f"{REDIS_CFG_PREFIX}{email.strip().lower()}"


def publish_global_send_limits() -> dict[str, int]:
    limits = global_send_limits()
    safe_cache_set(get_cache(), REDIS_GLOBAL_KEY, json.dumps(limits))
    return limits


def publish_account_send_limits(email: str, account: object | None) -> dict[str, Any]:
    """Write resolved limits for one mailbox to Redis."""
    email_key = email.strip().lower()
    if not email_key or "@" not in email_key:
        return {}
    disabled = account is not None and not bool(getattr(account, "is_active", True))
    limits: dict[str, Any] = resolve_send_limits(account)
    limits["disabled"] = bool(disabled)
    if disabled:
        # Suspended mailboxes cannot send at all.
        for field in LIMIT_FIELDS:
            limits[field] = 0
    safe_cache_set(get_cache(), _cfg_key(email_key), json.dumps(limits))
    return limits


def clear_account_send_limits(email: str) -> None:
    email_key = (email or "").strip().lower()
    if not email_key:
        return
    safe_cache_delete(get_cache(), _cfg_key(email_key))


def _map_line(email: str, limits: dict[str, Any]) -> str:
    return (
        f"{email} "
        f"burst={int(limits.get('send_rate_burst', 0) or 0)};"
        f"minute={int(limits.get('send_rate_per_minute', 0) or 0)};"
        f"hour={int(limits.get('send_rate_per_hour', 0) or 0)};"
        f"day={int(limits.get('send_rate_per_day', 0) or 0)};"
        f"week={int(limits.get('send_rate_per_week', 0) or 0)};"
        f"month={int(limits.get('send_rate_per_month', 0) or 0)};"
        f"disabled={'1' if limits.get('disabled') else '0'}"
    )


def write_send_limits_map(entries: Iterable[tuple[str, dict[str, Any]]]) -> Path | None:
    """Best-effort write of the Rspamd-readable map file."""
    try:
        MAP_FILE.parent.mkdir(parents=True, exist_ok=True)
        lines = [_map_line(email, limits) for email, limits in sorted(entries, key=lambda item: item[0])]
        content = "\n".join(lines) + ("\n" if lines else "")
        tmp = MAP_FILE.with_suffix(".map.tmp")
        tmp.write_text(content, encoding="utf-8")
        os.replace(tmp, MAP_FILE)
        os.chmod(MAP_FILE, 0o640)
        return MAP_FILE
    except OSError as exc:
        logger.warning("Unable to write send limits map %s: %s", MAP_FILE, exc)
        return None


def sync_all_account_send_limits(accounts: Iterable[object]) -> dict[str, Any]:
    """Rebuild Redis + map for all accounts and publish global defaults."""
    publish_global_send_limits()
    map_entries: list[tuple[str, dict[str, Any]]] = []
    synced = 0
    for account in accounts:
        email = account_email(account)
        if not email:
            continue
        limits = publish_account_send_limits(email, account)
        map_entries.append((email, limits))
        synced += 1
    map_path = write_send_limits_map(map_entries)
    return {"synced": synced, "map_file": str(map_path) if map_path else None, "global": global_send_limits()}
