"""
utils/json_db.py — Thread-safe JSON file storage
Same interface as a real DB layer — swap later without touching routes.
"""
from __future__ import annotations
import json, threading, uuid
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

_locks: dict[str, threading.Lock] = {}

def _lock(path: Path) -> threading.Lock:
    key = str(path.resolve())
    if key not in _locks:
        _locks[key] = threading.Lock()
    return _locks[key]

def _read(path: Path) -> list[dict]:
    path.parent.mkdir(parents=True, exist_ok=True)
    if not path.exists():
        return []
    try:
        with open(path, encoding="utf-8") as f:
            data = json.load(f)
            return data if isinstance(data, list) else []
    except json.JSONDecodeError:
        return []

def _write(path: Path, records: list[dict]) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    tmp = path.with_suffix(".tmp")
    with open(tmp, "w", encoding="utf-8") as f:
        json.dump(records, f, indent=2, default=str, ensure_ascii=False)
    tmp.replace(path)

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

def new_id() -> str:
    return str(uuid.uuid4())

def find_all(path: Path) -> list[dict]:
    with _lock(path):
        return list(_read(path))

def find_one(path: Path, **filters: Any) -> dict | None:
    with _lock(path):
        for rec in _read(path):
            if all(rec.get(k) == v for k, v in filters.items()):
                return dict(rec)
    return None

def find_many(path: Path, **filters: Any) -> list[dict]:
    with _lock(path):
        return [dict(r) for r in _read(path)
                if all(r.get(k) == v for k, v in filters.items())]

def find_by_id(path: Path, record_id: str) -> dict | None:
    with _lock(path):
        for rec in _read(path):
            if rec.get("id") == record_id:
                return dict(rec)
    return None

def insert(path: Path, record: dict) -> dict:
    with _lock(path):
        records = _read(path)
        record.setdefault("id", new_id())
        record.setdefault("created_at", now_iso())
        record.setdefault("updated_at", now_iso())
        records.append(record)
        _write(path, records)
        return dict(record)

def update(path: Path, record_id: str, updates: dict) -> dict | None:
    with _lock(path):
        records = _read(path)
        for i, rec in enumerate(records):
            if rec.get("id") == record_id:
                records[i] = {**rec, **updates, "updated_at": now_iso()}
                _write(path, records)
                return dict(records[i])
    return None

def upsert(path: Path, record: dict, key_field: str = "id") -> dict:
    key_val = record.get(key_field)
    if key_val:
        existing = find_one(path, **{key_field: key_val})
        if existing:
            return update(path, existing["id"], record)
    return insert(path, record)

def delete_by_id(path: Path, record_id: str) -> bool:
    with _lock(path):
        records = _read(path)
        new_records = [r for r in records if r.get("id") != record_id]
        if len(new_records) == len(records):
            return False
        _write(path, new_records)
        return True
