"""Local user database (JSON file).

Sprint 2.5: persists the list of Users + their hashed PINs to a single
JSON file (per Studio install). On the bridge display the same format
is consumed but typically managed by the Owner via the Studio UI.

Sprint 8 will migrate this to a signed/encrypted store bound to the HWID.
"""

from __future__ import annotations

import json
from pathlib import Path

from pydantic import TypeAdapter

from arautopilot.core.rbac import Role
from arautopilot.core.user import User


class UserStore:
    """Append/overwrite list of :class:`User` persisted to a JSON file."""

    def __init__(self, path: Path | str) -> None:
        self.path = Path(path)
        self._users: dict[str, User] = {}
        if self.path.exists():
            self._load()
        else:
            self.path.parent.mkdir(parents=True, exist_ok=True)

    # ----- Persistence ----------------------------------------------------
    def _load(self) -> None:
        text = self.path.read_text(encoding="utf-8")
        if not text.strip():
            return
        data = json.loads(text)
        if not isinstance(data, list):
            raise ValueError(f"{self.path}: expected a JSON list at the top level")
        adapter = TypeAdapter(list[User])
        users = adapter.validate_python(data)
        self._users = {u.user_id: u for u in users}

    def save(self) -> None:
        adapter = TypeAdapter(list[User])
        data = adapter.dump_python(list(self._users.values()), mode="json")
        self.path.write_text(
            json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8"
        )

    # ----- CRUD -----------------------------------------------------------
    def add(self, user: User) -> None:
        if user.user_id in self._users:
            raise ValueError(f"user_id {user.user_id!r} already exists")
        self._users[user.user_id] = user
        self.save()

    def remove(self, user_id: str) -> None:
        if user_id not in self._users:
            raise KeyError(user_id)
        del self._users[user_id]
        self.save()

    def replace(self, user: User) -> None:
        """Insert or update the user with this user_id."""
        self._users[user.user_id] = user
        self.save()

    def get(self, user_id: str) -> User | None:
        return self._users.get(user_id)

    def find_by_name(self, display_name: str) -> User | None:
        for u in self._users.values():
            if u.display_name == display_name:
                return u
        return None

    def all_users(self) -> list[User]:
        """Return every user, sorted by display_name."""
        return sorted(self._users.values(), key=lambda u: u.display_name.lower())

    def by_role(self, role: Role) -> list[User]:
        return [u for u in self.all_users() if u.role is role]

    def __len__(self) -> int:
        return len(self._users)

    def __contains__(self, user_id: object) -> bool:
        return user_id in self._users


def seed_demo_users(store: UserStore) -> None:
    """Populate a fresh store with one user of each role for first-run UX.

    Demo PINs (well-known, only used in dev/sample stores -- the user is
    expected to change these immediately):

        Super Admin "Alvaro"      PIN 1111
        Engineer    "Eng Demo"    PIN 2222
        Owner       "Captain"     PIN 3333
        User        "Crew"        PIN 4444
    """
    if len(store) > 0:
        return
    store.add(User.create(display_name="Alvaro",   role=Role.SUPER_ADMIN, pin="1111"))
    store.add(User.create(display_name="Eng Demo", role=Role.ENGINEER,    pin="2222"))
    store.add(User.create(display_name="Captain",  role=Role.OWNER,       pin="3333"))
    store.add(User.create(display_name="Crew",     role=Role.USER,        pin="4444"))
