"""
Server-side key, usage, and log storage.

Key/usage/log JSON files are stored outside the static web directory. They are
not exposed by any route; only this server process reads and writes them.
"""
import json
import os
import re
import secrets
import tempfile
import threading
from datetime import date, datetime, timedelta, timezone
from pathlib import Path
from typing import Optional

from config import cfg

_lock = threading.RLock()
DATA_DIR = Path(os.getenv("AMS_DATA_DIR", tempfile.gettempdir()))
KEY_PATH = DATA_DIR / "ams_keys.json"
USG_PATH = DATA_DIR / "ams_usage.json"
LOG_PATH = DATA_DIR / "ams_logs.json"
CTL_PATH = DATA_DIR / "ams_controller.json"
LOCAL_TZ = timezone(timedelta(hours=6), "Asia/Dhaka")


def _load(path, default):
    if not os.path.exists(path):
        return default
    with open(path, encoding="utf-8") as f:
        try:
            return json.load(f)
        except Exception:
            return default


def _save(path, data):
    Path(path).parent.mkdir(parents=True, exist_ok=True)
    with open(path, "w", encoding="utf-8") as f:
        json.dump(data, f, indent=2, default=str)


# ── API keys ─────────────────────────────────────────────────────────────────
def _load_keys() -> dict:
    first_run = not KEY_PATH.exists()
    keys = _load(KEY_PATH, {})
    if not isinstance(keys, dict):
        keys = {}

    if first_run:
        for api_key, rec in cfg.HARDCODED_KEYS.items():
            keys.setdefault(api_key, {**rec, "created_at": "seed", "source": "config"})
        _save(KEY_PATH, keys)
    return keys


def _clean_name(value: str) -> str:
    cleaned = re.sub(r"[^A-Za-z0-9_]+", "_", str(value).strip()).strip("_")
    return cleaned[:32] or "user"


def _key_short_name(value: str) -> str:
    cleaned = re.sub(r"[^A-Za-z0-9]+", "", str(value).strip()).upper()
    return (cleaned[:8] or "USER")


def create_key(name: str, nick: str, daily_limit: int, total_limit: int) -> dict:
    name = _clean_name(name)
    nick = str(nick or name).strip()[:64]
    short_name = _key_short_name(nick or name)
    daily_limit = max(int(daily_limit), 1)
    total_limit = max(int(total_limit), daily_limit)

    with _lock:
        keys = _load_keys()
        while True:
            token = secrets.token_urlsafe(18).replace("-", "").replace("_", "")[:20].upper()
            api_key = f"AMS-{short_name}-{token}"
            if api_key not in keys:
                break

        rec = {
            "name": name,
            "nick": nick,
            "daily_limit": daily_limit,
            "total_limit": total_limit,
            "created_at": datetime.utcnow().isoformat(),
            "source": "admin",
        }
        keys[api_key] = rec
        _save(KEY_PATH, keys)
        return {"api_key": api_key, **rec, **_get_usage(api_key)}


def update_key(api_key: str, patch: dict) -> Optional[dict]:
    with _lock:
        keys = _load_keys()
        rec = keys.get(api_key)
        if not rec:
            return None

        if "name" in patch and patch["name"] is not None:
            rec["name"] = _clean_name(patch["name"])
        if "nick" in patch and patch["nick"] is not None:
            rec["nick"] = str(patch["nick"] or rec.get("name", "user")).strip()[:64]
        if "daily_limit" in patch and patch["daily_limit"] is not None:
            rec["daily_limit"] = max(int(patch["daily_limit"]), 1)
        if "total_limit" in patch and patch["total_limit"] is not None:
            rec["total_limit"] = max(int(patch["total_limit"]), int(rec.get("daily_limit", 1)))
        rec["updated_at"] = datetime.utcnow().isoformat()
        keys[api_key] = rec
        _save(KEY_PATH, keys)
        return {"api_key": api_key, **rec, **_get_usage(api_key)}


def delete_key(api_key: str) -> bool:
    with _lock:
        keys = _load_keys()
        if api_key not in keys:
            return False
        del keys[api_key]
        _save(KEY_PATH, keys)
        return True


def get_key(api_key: str) -> Optional[dict]:
    rec = _load_keys().get(api_key)
    if not rec:
        return None
    usage = _get_usage(api_key)
    return {**rec, **usage, "api_key": api_key}


def get_all_keys() -> list:
    result = []
    for api_key, rec in _load_keys().items():
        usage = _get_usage(api_key)
        result.append({"api_key": api_key, **rec, **usage})
    return result


# ── Usage tracking ───────────────────────────────────────────────────────────
def _get_usage(api_key: str) -> dict:
    with _lock:
        data = _load(USG_PATH, {})
        today = str(date.today())
        usg = data.get(api_key, {})
        if usg.get("last_reset") != today:
            usg = {
                "used_today": 0,
                "total_used": usg.get("total_used", 0),
                "last_reset": today,
            }
            data[api_key] = usg
            _save(USG_PATH, data)
        return usg


def increment_usage(api_key: str, cut: int):
    with _lock:
        data = _load(USG_PATH, {})
        today = str(date.today())
        usg = data.get(api_key, {"used_today": 0, "total_used": 0, "last_reset": today})
        if usg.get("last_reset") != today:
            usg["used_today"] = 0
            usg["last_reset"] = today
        usg["used_today"] = usg.get("used_today", 0) + cut
        usg["total_used"] = usg.get("total_used", 0) + cut
        data[api_key] = usg
        _save(USG_PATH, data)


def reset_daily_all():
    with _lock:
        data = _load(USG_PATH, {})
        today = str(date.today())
        for usg in data.values():
            usg["used_today"] = 0
            usg["last_reset"] = today
        _save(USG_PATH, data)


def reset_key_usage(api_key: str):
    with _lock:
        data = _load(USG_PATH, {})
        if api_key in data:
            data[api_key]["used_today"] = 0
            data[api_key]["total_used"] = 0
            data[api_key]["last_reset"] = str(date.today())
            _save(USG_PATH, data)


# ── Logs ─────────────────────────────────────────────────────────────────────
def write_log(entry: dict):
    with _lock:
        logs = _load(LOG_PATH, [])
        entry["timestamp"] = datetime.utcnow().isoformat()
        logs.append(entry)
        if len(logs) > 500:
            logs = logs[-500:]
        _save(LOG_PATH, logs)


def get_logs(limit: int = 50) -> list:
    with _lock:
        logs = _load(LOG_PATH, [])
        return list(reversed(logs))[:limit]


def get_logs_by_key(api_key: str, limit: int = 20) -> list:
    with _lock:
        logs = _load(LOG_PATH, [])
        filtered = [log for log in logs if log.get("api_key") == api_key]
        return list(reversed(filtered))[:limit]


def _log_local_date(log: dict) -> date | None:
    ts = str(log.get("timestamp") or "")
    if not ts:
        return None
    try:
        dt = datetime.fromisoformat(ts.replace("Z", "+00:00"))
    except ValueError:
        return None
    if dt.tzinfo is None:
        dt = dt.replace(tzinfo=timezone.utc)
    return dt.astimezone(LOCAL_TZ).date()


def get_today_stats() -> dict:
    with _lock:
        logs = _load(LOG_PATH, [])
    today = datetime.now(LOCAL_TZ).date()
    stats = {
        "date": today.isoformat(),
        "requests": 0,
        "success_requests": 0,
        "failed_requests": 0,
        "likes_sent": 0,
        "limit_cut": 0,
        "by_endpoint": {},
    }
    for log in logs:
        if _log_local_date(log) != today:
            continue
        endpoint = str(log.get("endpoint") or "unknown")
        success = int(log.get("success") or 0)
        cut = int(log.get("limit_cut") or 0)
        stats["requests"] += 1
        stats["likes_sent"] += success
        stats["limit_cut"] += cut
        if log.get("error"):
            stats["failed_requests"] += 1
        else:
            stats["success_requests"] += 1
        ep = stats["by_endpoint"].setdefault(
            endpoint,
            {"requests": 0, "success_requests": 0, "failed_requests": 0, "likes_sent": 0, "limit_cut": 0},
        )
        ep["requests"] += 1
        ep["likes_sent"] += success
        ep["limit_cut"] += cut
        if log.get("error"):
            ep["failed_requests"] += 1
        else:
            ep["success_requests"] += 1
    return stats


def get_controller_settings(default_100: int, default_200: int) -> dict:
    with _lock:
        data = _load(CTL_PATH, {})
    return {
        "thresholds": {
            "like100": int(data.get("thresholds", {}).get("like100", default_100)),
            "like200": int(data.get("thresholds", {}).get("like200", default_200)),
        }
    }


def set_controller_thresholds(like100: int | None = None, like200: int | None = None) -> dict:
    with _lock:
        data = _load(CTL_PATH, {})
        thresholds = data.setdefault("thresholds", {})
        if like100 is not None:
            thresholds["like100"] = max(int(like100), 0)
        if like200 is not None:
            thresholds["like200"] = max(int(like200), 0)
        _save(CTL_PATH, data)
        return {"thresholds": dict(thresholds)}
