# retoor <retoor@molodetz.nl>

from __future__ import annotations

import re
from dataclasses import dataclass

from devplacepy.database import (
    SOFT_DELETE_TABLES,
    db,
    get_setting,
)
from devplacepy.utils import _user_from_api_key, get_current_user, is_primary_admin

TABLE_NAME = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
DEFAULT_DENY = {"sessions", "password_resets", "cache_state"}


class DbApiDenied(Exception):
    pass


class DbApiBadTable(Exception):
    pass


@dataclass(frozen=True)
class Caller:
    kind: str
    uid: str
    username: str
    api_key: str


def deny_tables() -> set[str]:
    raw = get_setting("dbapi_deny_tables", "")
    extra = {name.strip() for name in raw.split(",") if name.strip()}
    return DEFAULT_DENY | extra


def _bearer_key(request) -> str:
    key = request.headers.get("x-api-key", "").strip()
    if key:
        return key
    scheme, _, credentials = request.headers.get("authorization", "").partition(" ")
    if scheme.lower() == "bearer":
        return credentials.strip()
    return ""


def caller_for(request) -> Caller | None:
    user = get_current_user(request)
    if user and is_primary_admin(user):
        return Caller(
            kind="admin",
            uid=user["uid"],
            username=user.get("username", ""),
            api_key=user.get("api_key", "") or "",
        )
    key = _bearer_key(request)
    if key:
        keyed = _user_from_api_key(key)
        if keyed and is_primary_admin(keyed):
            return Caller(
                kind="admin",
                uid=keyed["uid"],
                username=keyed.get("username", ""),
                api_key=keyed.get("api_key", "") or "",
            )
    return None


def require_caller(request) -> Caller:
    caller = caller_for(request)
    if caller is None:
        raise DbApiDenied("Primary-administrator access required.")
    return caller


def soft_delete_aware(table: str) -> bool:
    return table in SOFT_DELETE_TABLES


def assert_table(name: str) -> str:
    if not name or not TABLE_NAME.match(name):
        raise DbApiBadTable(f"Invalid table name: {name!r}")
    if name in deny_tables():
        raise DbApiBadTable(f"Table {name!r} is not accessible through the database API.")
    if name not in db.tables:
        raise DbApiBadTable(f"Unknown table: {name!r}")
    return name


def allowed_tables() -> list[str]:
    blocked = deny_tables()
    return sorted(name for name in db.tables if name not in blocked)
