"""RFC 3161 timestamp anchoring — a third-party existence proof for a tree head.

Why this exists: a witness the operator co-hosts is defense-in-depth, not proof
against the operator (they hold both keys). An RFC 3161 Time-Stamp Authority is a
*trusted third party the operator does not control*, and under eIDAS Art. 41 a
qualified timestamp carries a legal presumption of the time and integrity of the
data it binds. Anchoring the signed tree head to a TSA therefore proves "this log
root existed, unaltered, by time T" to an auditor — the thing a self-signed head
cannot establish. Free public TSAs (FreeTSA, DigiCert) accept a plain HTTP POST.

We build the RFC 3161 ``TimeStampReq`` with a tiny self-contained DER encoder
(no new dependency): the request is a small, fully-specified ASN.1 structure. The
TSA's response token is stored verbatim (base64) alongside the imprint, so an
auditor can re-verify it later with any RFC 3161 tool. Parsing/validating the
returned token's signature is intentionally out of scope here — that is the
verifier's job with the TSA's certificate, and keeping it out keeps this module
dependency-light and honest about what it does (obtain + store the token).
"""
from __future__ import annotations

import base64
import hashlib
import secrets

from .audit import canonical_json

# OID 2.16.840.1.101.3.4.2.1  = sha256
_SHA256_OID = (2, 16, 840, 1, 101, 3, 4, 2, 1)
_DEFAULT_TIMEOUT = 30.0


class TimestampError(Exception):
    """Raised when a timestamp could not be obtained from the TSA."""


# --------------------------------------------------------------------------
# minimal DER encoder (only the pieces a TimeStampReq needs)
# --------------------------------------------------------------------------
def _der_len(n: int) -> bytes:
    if n < 0x80:
        return bytes([n])
    out = b""
    while n:
        out = bytes([n & 0xFF]) + out
        n >>= 8
    return bytes([0x80 | len(out)]) + out


def _der(tag: int, body: bytes) -> bytes:
    return bytes([tag]) + _der_len(len(body)) + body


def _der_int(n: int) -> bytes:
    if n == 0:
        return _der(0x02, b"\x00")
    raw = b""
    v = n
    while v:
        raw = bytes([v & 0xFF]) + raw
        v >>= 8
    if raw[0] & 0x80:  # keep it positive
        raw = b"\x00" + raw
    return _der(0x02, raw)


def _der_oid(parts: tuple[int, ...]) -> bytes:
    first = 40 * parts[0] + parts[1]
    body = bytes([first])
    for p in parts[2:]:
        if p < 0x80:
            body += bytes([p])
        else:
            chunk = bytes([p & 0x7F])
            p >>= 7
            while p:
                chunk = bytes([(p & 0x7F) | 0x80]) + chunk
                p >>= 7
            body += chunk
    return _der(0x06, body)


# --------------------------------------------------------------------------
# head -> message imprint -> TimeStampReq
# --------------------------------------------------------------------------
def _head_digest(head: dict) -> bytes:
    """SHA-256 over the canonicalized (tree_size, root_hash) — the exact bytes the
    timestamp is bound to, so the token proves *this* root existed."""
    canon = canonical_json(
        {"tree_size": head["tree_size"], "root_hash": head["root_hash"]}
    ).encode("utf-8")
    return hashlib.sha256(canon).digest()


def build_tsq(head: dict, *, nonce: int | None = None) -> tuple[bytes, int]:
    """Build a DER-encoded RFC 3161 ``TimeStampReq`` for ``head``.

    Returns (der_bytes, nonce). Structure (RFC 3161 §2.4.1):
        TimeStampReq ::= SEQUENCE {
            version        INTEGER { v1(1) },
            messageImprint MessageImprint,          -- {algId, hashedMessage}
            nonce          INTEGER OPTIONAL,
            certReq        BOOLEAN DEFAULT FALSE }
    """
    if nonce is None:
        nonce = secrets.randbits(64)
    digest = _head_digest(head)

    alg_id = _der(0x30, _der_oid(_SHA256_OID) + _der(0x05, b""))  # AlgorithmIdentifier (NULL params)
    message_imprint = _der(0x30, alg_id + _der(0x04, digest))     # SEQUENCE { algId, OCTET STRING }

    body = (
        _der_int(1)                    # version v1
        + message_imprint
        + _der_int(nonce)
        + _der(0x01, b"\xff")          # certReq = TRUE (ask the TSA to include its cert)
    )
    return _der(0x30, body), nonce


# --------------------------------------------------------------------------
# transport
# --------------------------------------------------------------------------
def _http_post(url: str, tsq: bytes, timeout: float) -> bytes:
    """POST a DER TimeStampReq to a TSA and return the raw response bytes."""
    try:
        import httpx
    except ModuleNotFoundError as exc:  # pragma: no cover - env guard
        raise TimestampError(
            "RFC 3161 anchoring needs httpx — install it with: pip install 'abom-cli[mcp]'"
        ) from exc
    resp = httpx.post(
        url, content=tsq,
        headers={"Content-Type": "application/timestamp-query"},
        timeout=timeout,
    )
    resp.raise_for_status()
    return resp.content


def parse_tsr(tsr: bytes) -> bytes:
    """Return the raw response bytes as the stored token.

    We deliberately do not crack the ASN.1 open here — the token is stored
    verbatim and re-verified downstream against the TSA's certificate. This
    function exists as the seam where richer validation would live.
    """
    if not tsr:
        raise TimestampError("TSA returned an empty response (no token)")
    return tsr


def anchor_head(head: dict, *, tsa_url: str, timeout: float = _DEFAULT_TIMEOUT, _post=None) -> dict:
    """Obtain an RFC 3161 timestamp over ``head`` from ``tsa_url``.

    Returns a self-describing record an auditor can re-verify offline later:
    the TSA URL, the (tree_size, root_hash) it binds, the message imprint, and
    the TSA's signed token (base64). ``_post`` is injectable for testing.
    """
    post = _post or _http_post
    tsq, _nonce = build_tsq(head)
    try:
        tsr = post(tsa_url, tsq, timeout)
    except TimestampError:
        raise
    except Exception as exc:
        raise TimestampError(f"could not reach TSA {tsa_url}: {exc}") from exc

    token = parse_tsr(tsr)
    return {
        "tsa_url": tsa_url,
        "tree_size": head["tree_size"],
        "root_hash": head["root_hash"],
        "hash_alg": "sha256",
        "message_imprint_sha256": _head_digest(head).hex(),
        "timestamp_token_b64": base64.b64encode(token).decode(),
    }
