"""User accounts + credit balances for Advanced Glass Design Studio.

Plain JSON file storage (no database needed) — fine for a small/medium
number of users on shared hosting. Accounts and credits are managed from
the backend only (there is no public sign-up): use manage_users.py on the
server to create a user or top up their credits.

Storage lives at CONFIG_DIR/users.json, next to the app's existing
config.json (see app_config.py).
"""
from __future__ import annotations

import json
import threading
from datetime import datetime, timezone

from werkzeug.security import check_password_hash, generate_password_hash

import app_config

USERS_FILE = app_config.CONFIG_DIR / "users.json"
_lock = threading.Lock()

# ---------------------------------------------------------------------------
# Per-user API keys ("My API Keys" in the app's Settings modal). Each user can
# add their own provider keys; only that user (and the admin) can see them.
# Generation keeps these keys isolated to their owner; backend-owned assigned
# and global-fallback providers are stored separately in config.json.
# ---------------------------------------------------------------------------

def _ensure_providers(record: dict) -> list:
    if not isinstance(record.get("providers"), list):
        record["providers"] = []
    return record["providers"]


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


def load_users() -> dict:
    if not USERS_FILE.exists():
        return {}
    try:
        return json.loads(USERS_FILE.read_text(encoding="utf-8"))
    except (json.JSONDecodeError, OSError):
        return {}


def save_users(users: dict) -> None:
    app_config.CONFIG_DIR.mkdir(parents=True, exist_ok=True)
    USERS_FILE.write_text(json.dumps(users, indent=2), encoding="utf-8")


def user_exists(username: str) -> bool:
    return username in load_users()


def create_user(username: str, password: str, credits: int = 0, tester: bool = False) -> dict:
    username = (username or "").strip()
    if not username:
        raise ValueError("Username cannot be empty.")
    if not password:
        raise ValueError("Password cannot be empty.")
    with _lock:
        users = load_users()
        if username in users:
            raise ValueError(f"User '{username}' already exists.")
        users[username] = {
            "password_hash": generate_password_hash(password),
            "credits": int(credits),
            "created_at": _now(),
            "providers": [],
            "tester": bool(tester),
        }
        save_users(users)
        return users[username]


def delete_user(username: str) -> bool:
    with _lock:
        users = load_users()
        if username not in users:
            return False
        del users[username]
        save_users(users)
        return True


def verify_login(username: str, password: str) -> bool:
    record = load_users().get(username)
    if not record:
        return False
    return check_password_hash(record["password_hash"], password)


def get_credits(username: str):
    record = load_users().get(username or "")
    return record["credits"] if record else None


def add_credits(username: str, delta: int) -> int:
    """Adds (or, with a negative delta, removes) credits. Returns the new balance."""
    with _lock:
        users = load_users()
        if username not in users:
            raise ValueError(f"User '{username}' does not exist.")
        users[username]["credits"] = max(0, users[username]["credits"] + int(delta))
        save_users(users)
        return users[username]["credits"]


def set_credits(username: str, amount: int) -> int:
    with _lock:
        users = load_users()
        if username not in users:
            raise ValueError(f"User '{username}' does not exist.")
        users[username]["credits"] = max(0, int(amount))
        save_users(users)
        return users[username]["credits"]


def deduct_credits(username: str, amount: int) -> bool:
    """Spends `amount` credits if the balance covers it. Returns False (no-op) otherwise."""
    with _lock:
        users = load_users()
        record = users.get(username)
        if not record or record["credits"] < amount:
            return False
        record["credits"] -= amount
        save_users(users)
        return True


def is_tester(username: str) -> bool:
    """Tester accounts (admin-only users) can still use the app while maintenance mode is on."""
    record = load_users().get(username or "")
    return bool(record and record.get("tester"))


def set_tester(username: str, value: bool) -> bool:
    with _lock:
        users = load_users()
        if username not in users:
            return False
        users[username]["tester"] = bool(value)
        save_users(users)
        return True


def change_password(username: str, new_password: str) -> bool:
    with _lock:
        users = load_users()
        if username not in users:
            return False
        users[username]["password_hash"] = generate_password_hash(new_password)
        save_users(users)
        return True


def list_users() -> list[dict]:
    users = load_users()
    return [
        {
            "username": name,
            "credits": rec["credits"],
            "created_at": rec.get("created_at"),
            "key_count": len(rec.get("providers") or []),
            "tester": bool(rec.get("tester")),
        }
        for name, rec in sorted(users.items())
    ]


# ---------------------------------------------------------------------------
# Per-user provider (API key) CRUD — mirrors app_config.py's shape
# ({id, type, label, api_key, model}) so ai_client.py / the rotation pool in
# app.py can treat a user-owned provider exactly like an admin one.
# ---------------------------------------------------------------------------

def list_user_providers(username: str) -> list[dict]:
    record = load_users().get(username or "")
    return list(record.get("providers") or []) if record else []


def get_user_provider(username: str, provider_id: str):
    for p in list_user_providers(username):
        if p.get("id") == provider_id:
            return p
    return None


def add_user_provider(username: str, provider: dict) -> dict:
    with _lock:
        users = load_users()
        if username not in users:
            raise ValueError(f"User '{username}' does not exist.")
        providers = _ensure_providers(users[username])
        providers.append(provider)
        save_users(users)
        return provider


def update_user_provider(username: str, provider_id: str, **fields):
    with _lock:
        users = load_users()
        record = users.get(username)
        if not record:
            return None
        for p in _ensure_providers(record):
            if p.get("id") == provider_id:
                p.update(fields)
                save_users(users)
                return p
        return None


def delete_user_provider(username: str, provider_id: str) -> bool:
    with _lock:
        users = load_users()
        record = users.get(username)
        if not record:
            return False
        providers = _ensure_providers(record)
        before = len(providers)
        record["providers"] = [p for p in providers if p.get("id") != provider_id]
        if len(record["providers"]) == before:
            return False
        save_users(users)
        return True


def all_user_providers() -> list[dict]:
    """Every user's own keys, each tagged with 'owner', flattened into one list —
    used to build the shared generation pool and for the admin's full-visibility view."""
    out = []
    for name, rec in load_users().items():
        for p in (rec.get("providers") or []):
            tagged = dict(p)
            tagged["owner"] = name
            out.append(tagged)
    return out
