from __future__ import annotations

import fcntl
import json
import os
import stat
from contextlib import contextmanager
from pathlib import Path
from typing import Any, Callable, Iterator

VAULT_VERSION = 1


def resolve_credentials_path(home: Path, cfg: dict[str, Any] | None = None) -> Path:
    env_path = os.environ.get("NIANNIAN_CREDENTIALS_FILE")
    if env_path:
        return Path(env_path).expanduser().resolve()
    persist = os.environ.get("NIANNIAN_PERSIST_DIR")
    cred_cfg = ((cfg or {}).get("storage") or {}).get("credentials") or {}
    rel = cred_cfg.get("file", "cloud/credentials.json")
    if persist:
        return (Path(persist).expanduser() / str(rel)).resolve()
    return (home / str(rel)).resolve()


def default_vault() -> dict[str, Any]:
    return {"version": VAULT_VERSION}


def read_vault(path: Path) -> dict[str, Any]:
    if not path.is_file():
        return default_vault()
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
        data.setdefault("version", VAULT_VERSION)
        return data
    except (json.JSONDecodeError, OSError):
        return default_vault()


def write_vault(path: Path, data: dict[str, Any]) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    tmp = path.with_suffix(".tmp")
    payload = json.dumps(data, ensure_ascii=False, indent=2) + "\n"
    tmp.write_text(payload, encoding="utf-8")
    os.replace(tmp, path)
    try:
        path.chmod(stat.S_IRUSR | stat.S_IWUSR)
    except OSError:
        pass


@contextmanager
def locked_vault(path: Path) -> Iterator[dict[str, Any]]:
    path.parent.mkdir(parents=True, exist_ok=True)
    if not path.is_file():
        write_vault(path, default_vault())
    with path.open("r+", encoding="utf-8") as fh:
        fcntl.flock(fh.fileno(), fcntl.LOCK_EX)
        try:
            fh.seek(0)
            raw = fh.read()
            data = json.loads(raw) if raw.strip() else default_vault()
            data.setdefault("version", VAULT_VERSION)
            yield data
            fh.seek(0)
            fh.truncate()
            fh.write(json.dumps(data, ensure_ascii=False, indent=2) + "\n")
            fh.flush()
        finally:
            fcntl.flock(fh.fileno(), fcntl.LOCK_UN)
    try:
        path.chmod(stat.S_IRUSR | stat.S_IWUSR)
    except OSError:
        pass


def update_vault(path: Path, mutator: Callable[[dict[str, Any]], None]) -> dict[str, Any]:
    with locked_vault(path) as data:
        mutator(data)
        return dict(data)
