"""Escrow ceremony for the backup-signing public key.

Backups are authenticated with an Ed25519 signature over SHA256SUMS. The private
half stays on the server; the public half is what an operator needs in order to
verify a backup during disaster recovery — on a machine where this server, and
everything on it, is gone.

That makes the escrow the weak link: option B is worthless if nobody ever saved
the key. So the installer prints it and writes it to a file outside the install
tree, and the first admin session has to paste it back before the panel, the API
or the interactive CLI will do anything else.

What this is, and what it is NOT
--------------------------------
This is a FORCING FUNCTION, not an authentication factor. The key is public by
construction and a copy sits on this same server, so anyone with root can read it
and satisfy the prompt. It proves only that the operator has the key in front of
them — which is exactly the property that makes a restore possible later, and the
only property being claimed.

Because it is not a security boundary, it deliberately fails toward recoverability:
`limristem-mail backup show-signing-key` prints the value at any time, so a lost
copy is an inconvenience rather than a locked-out mail server.
"""

from __future__ import annotations

import logging
import os
import re
from pathlib import Path

from .settings import get_settings

logger = logging.getLogger(__name__)

SIGNING_PUBLIC_KEY_FILENAME = "backup-signing.pub"
ESCROW_FILENAME = "limristem-mail-backup-signing.pub"
_ACK_MARKER = "backup-signing-key.acknowledged"
_PEM_BODY_RE = re.compile(r"-----BEGIN PUBLIC KEY-----(.*?)-----END PUBLIC KEY-----", re.S)


def _config_dir() -> Path:
    return get_settings().base_dir / "config"


def signing_public_key_path() -> Path:
    return _config_dir() / SIGNING_PUBLIC_KEY_FILENAME


def acknowledgement_marker_path() -> Path:
    """Marker lives in the state dir, which the service account may write.

    Deliberate: the gate is an operator ceremony, not a privilege boundary, so it
    does not need to resist the service account — and putting it in config/ would
    mean the API could not record the acknowledgement at all.
    """
    state_dir = os.getenv("LIMRISTEM_MAIL_STATE_DIR", "/var/lib/limristem-mail/state")
    return Path(state_dir) / _ACK_MARKER


def signing_public_key() -> str | None:
    """The stored public key, or None when signing has not been set up yet."""
    path = signing_public_key_path()
    try:
        raw = path.read_text(encoding="utf-8").strip()
    except OSError:
        return None
    return raw or None


def normalize_public_key(value: str | None) -> str | None:
    """Reduce a PEM public key to its base64 body.

    Operators paste keys out of terminals and password managers, so the comparison
    must survive re-wrapping, stray whitespace and CRLF. Only the body is compared;
    the armour lines carry no information.
    """
    if not value:
        return None
    match = _PEM_BODY_RE.search(value)
    body = match.group(1) if match else value
    collapsed = "".join(body.split())
    return collapsed or None


def is_configured() -> bool:
    return signing_public_key() is not None


def is_acknowledged() -> bool:
    """Whether the operator has confirmed they hold a copy of the public key."""
    try:
        return acknowledgement_marker_path().is_file()
    except OSError:
        return False


def gate_is_open() -> bool:
    """Whether admin surfaces may proceed.

    Open when there is no key to escrow yet (a fresh install that has never run a
    backup): the ceremony must never block a system that has nothing to confirm.
    """
    return not is_configured() or is_acknowledged()


def acknowledge(pasted: str) -> bool:
    """Record the acknowledgement if `pasted` matches the stored public key."""
    expected = normalize_public_key(signing_public_key())
    supplied = normalize_public_key(pasted)
    if not expected or not supplied or expected != supplied:
        return False
    marker = acknowledgement_marker_path()
    try:
        marker.parent.mkdir(parents=True, exist_ok=True)
        marker.write_text(
            "The backup-signing public key was confirmed present by an operator.\n",
            encoding="utf-8",
        )
    except OSError as exc:
        logger.warning("Unable to record backup-key acknowledgement: %s", exc)
        return False
    return True


def escrow_copy_paths() -> list[Path]:
    """Where the installer drops a readable copy, most durable first.

    Outside the install tree where possible: a reinstall or a wipe of $BASE_DIR
    must not take the operator's only copy with it.
    """
    candidates: list[Path] = [Path("/root") / ESCROW_FILENAME]
    sudo_user_home = os.getenv("SUDO_USER")
    if sudo_user_home:
        try:
            import pwd

            candidates.append(Path(pwd.getpwnam(sudo_user_home).pw_dir) / ESCROW_FILENAME)
        except (KeyError, ImportError):
            pass
    candidates.append(get_settings().base_dir / ESCROW_FILENAME)
    return candidates
