"""
services/user_store.py
────────────────────────────────────────────────────────────────
JSON-file-backed user store.
  data/users.json  →  list of user records
Replace this module with a SQLAlchemy model when you move to a real DB.

Schema per user:
{
  "id":         "uuid4",
  "username":   "alice",
  "email":      "alice@example.com",
  "full_name":  "Alice Smith",
  "role":       "admin" | "learner",
  "hashed_password": "$2b$...",
  "active":     true,
  "created_at": "ISO-8601",
  "updated_at": "ISO-8601",
  "last_login": "ISO-8601" | null
}
"""
from __future__ import annotations

import asyncio
import json
import uuid
from datetime import datetime, timezone
from pathlib import Path
from typing import Dict, List, Optional

from passlib.context import CryptContext

_LOCK = asyncio.Lock()
_DATA_DIR = Path(__file__).parent.parent / "data"
_DATA_DIR.mkdir(exist_ok=True)
_USERS_FILE = _DATA_DIR / "users.json"

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")


def _now() -> str:
    return datetime.now(timezone.utc).isoformat()


# ── Low-level IO ──────────────────────────────────────────────────────────────

async def _read() -> List[Dict]:
    async with _LOCK:
        if not _USERS_FILE.exists():
            return []
        try:
            return json.loads(_USERS_FILE.read_text(encoding="utf-8"))
        except Exception:
            return []


async def _write(data: List[Dict]) -> None:
    async with _LOCK:
        _USERS_FILE.write_text(
            json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8"
        )


# ── Bootstrap ─────────────────────────────────────────────────────────────────

async def bootstrap_admin(username: str, password: str) -> None:
    """
    Called at startup.  Creates the default admin account only if no
    admin user exists yet — safe to call every restart.
    """
    users = await _read()
    if any(u.get("role") == "admin" for u in users):
        return          # admin already exists — do nothing
    record = {
        "id":              str(uuid.uuid4()),
        "username":        username,
        "email":           f"{username}@rolepilot.local",
        "full_name":       "System Admin",
        "role":            "admin",
        "hashed_password": pwd_context.hash(password),
        "active":          True,
        "created_at":      _now(),
        "updated_at":      _now(),
        "last_login":      None,
    }
    users.insert(0, record)
    await _write(users)


# ── Read ──────────────────────────────────────────────────────────────────────

async def get_by_username(username: str) -> Optional[Dict]:
    users = await _read()
    for u in users:
        if u.get("username") == username and u.get("active", True):
            return u
    return None


async def get_by_id(uid: str) -> Optional[Dict]:
    users = await _read()
    for u in users:
        if u.get("id") == uid:
            return u
    return None


async def list_users(role: Optional[str] = None) -> List[Dict]:
    users = await _read()
    if role:
        users = [u for u in users if u.get("role") == role]
    # Never expose hashed_password to callers
    return [_safe(u) for u in users]


def _safe(u: Dict) -> Dict:
    """Strip password hash before returning to API layer."""
    return {k: v for k, v in u.items() if k != "hashed_password"}


# ── Write ─────────────────────────────────────────────────────────────────────

async def create_user(
    username: str,
    password: str,
    role: str = "learner",
    email: str = "",
    full_name: str = "",
) -> Dict:
    users = await _read()
    # Uniqueness check
    if any(u.get("username") == username for u in users):
        raise ValueError(f"Username '{username}' already exists")
    record = {
        "id":              str(uuid.uuid4()),
        "username":        username,
        "email":           email or f"{username}@rolepilot.local",
        "full_name":       full_name or username.title(),
        "role":            role,
        "hashed_password": pwd_context.hash(password),
        "active":          True,
        "created_at":      _now(),
        "updated_at":      _now(),
        "last_login":      None,
    }
    users.insert(0, record)
    await _write(users)
    return _safe(record)


async def update_user(uid: str, patch: Dict) -> Optional[Dict]:
    """
    Update any fields except id / created_at.
    Pass 'password' in patch to rehash and store.
    """
    users = await _read()
    for i, u in enumerate(users):
        if u.get("id") == uid:
            if "password" in patch:
                patch["hashed_password"] = pwd_context.hash(patch.pop("password"))
            updated = {**u, **patch, "id": uid, "updated_at": _now()}
            users[i] = updated
            await _write(users)
            return _safe(updated)
    return None


async def delete_user(uid: str) -> bool:
    users = await _read()
    new = [u for u in users if u.get("id") != uid]
    if len(new) == len(users):
        return False
    await _write(new)
    return True


async def record_login(uid: str) -> None:
    users = await _read()
    for i, u in enumerate(users):
        if u.get("id") == uid:
            users[i]["last_login"] = _now()
            await _write(users)
            return


# ── Auth helpers ──────────────────────────────────────────────────────────────

def verify_password(plain: str, hashed: str) -> bool:
    return pwd_context.verify(plain, hashed)


async def authenticate(username: str, password: str) -> Optional[Dict]:
    user = await get_by_username(username)
    if user and verify_password(password, user.get("hashed_password", "")):
        await record_login(user["id"])
        return _safe(user)
    return None
