"""Server-wide secrets that site admins set in the app (the Anthropic API key).

Stored encrypted (Fernet) with a key derived from APP_SECRET_KEY, which lives
only in the server env file — so database backups never hold a usable key.
The plaintext is never returned by the API; only its last four characters.
A key saved in the app takes precedence over ANTHROPIC_API_KEY in the env file.
"""

from __future__ import annotations

import base64
import hashlib

from cryptography.fernet import Fernet, InvalidToken
from psycopg import Connection

from .db import fetchone
from .settings import get_settings

ANTHROPIC_KEY = "anthropic_api_key"


class SecretsUnavailable(Exception):
    """APP_SECRET_KEY is not set, so secrets cannot be stored or read."""


def _fernet() -> Fernet:
    secret = get_settings().app_secret_key
    if not secret:
        raise SecretsUnavailable("APP_SECRET_KEY is not set on this server.")
    return Fernet(base64.urlsafe_b64encode(hashlib.sha256(secret.encode()).digest()))


def ready() -> bool:
    return bool(get_settings().app_secret_key)


def put(conn: Connection, name: str, value: str, user_id: int) -> None:
    token = _fernet().encrypt(value.encode()).decode()
    conn.execute(
        """INSERT INTO app_secrets (name, ciphertext, last4, updated_by) VALUES (%s, %s, %s, %s)
           ON CONFLICT (name) DO UPDATE SET ciphertext = EXCLUDED.ciphertext, last4 = EXCLUDED.last4,
               updated_by = EXCLUDED.updated_by, updated_at = now()""",
        (name, token, value[-4:], user_id),
    )


def delete(conn: Connection, name: str) -> None:
    conn.execute("DELETE FROM app_secrets WHERE name = %s", (name,))


def get(conn: Connection, name: str) -> str | None:
    row = fetchone(conn, "SELECT ciphertext FROM app_secrets WHERE name = %s", (name,))
    if not row or not ready():
        return None
    try:
        return _fernet().decrypt(row["ciphertext"].encode()).decode()
    except InvalidToken:
        return None  # APP_SECRET_KEY changed since it was saved; the admin must re-enter it


def anthropic_key(conn: Connection) -> str | None:
    """The key to use: saved in the app first, then the env file."""
    return get(conn, ANTHROPIC_KEY) or get_settings().anthropic_api_key or None


def anthropic_status(conn: Connection) -> dict:
    row = fetchone(
        conn,
        """SELECT s.last4, s.updated_at, u.name AS updated_by FROM app_secrets s
           LEFT JOIN users u ON u.id = s.updated_by WHERE s.name = %s""",
        (ANTHROPIC_KEY,),
    )
    saved_ok = bool(row) and get(conn, ANTHROPIC_KEY) is not None
    env_key = get_settings().anthropic_api_key
    if saved_ok:
        source, last4 = "app", row["last4"]
    elif env_key:
        source, last4 = "env", env_key[-4:]
    else:
        source, last4 = None, None
    return {
        "configured": source is not None,
        "source": source,
        "last4": last4,
        "updated_at": row["updated_at"] if row else None,
        "updated_by": row["updated_by"] if row else None,
        "unreadable": bool(row) and not saved_ok,  # saved, but APP_SECRET_KEY is missing or changed
        "secrets_ready": ready(),
    }
