"""RFC 3161 timestamp anchoring of a signed tree head.

This is the part that earns evidentiary weight against the *operator*. A witness
the operator co-hosts is defense-in-depth (see test_witness.py); an RFC 3161
timestamp is issued by an independent Time-Stamping Authority 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. So anchoring the
tree head to a TSA proves "this log root existed, unaltered, by time T" to a
third party — which a self-signed head cannot.

The TSA is a network service, so the request builder / response parser are pure
and the HTTP POST is injected: tests exercise the full request/response handling
against a canned TSA reply, with no network.
"""
import hashlib

import pytest

from abom.anchor import (
    TimestampError,
    build_tsq,
    anchor_head,
)


def _head():
    return {"tree_size": 3, "root_hash": "ab" * 32}


# ---------------------------------------------------------------------------
# request building (RFC 3161 TimeStampReq)
# ---------------------------------------------------------------------------
def test_build_tsq_hashes_the_head_deterministically():
    head = _head()
    tsq1, nonce1 = build_tsq(head)
    tsq2, _ = build_tsq(head, nonce=nonce1)
    assert isinstance(tsq1, bytes) and tsq1  # DER-encoded request bytes
    assert tsq1 == tsq2                       # same head + nonce -> same request


def test_build_tsq_binds_the_exact_head_bytes():
    # the message imprint must be the sha256 of the canonicalized head, so the
    # timestamp is bound to THIS root, not some other
    from abom.anchor import _head_digest
    from abom.audit import canonical_json
    expected = hashlib.sha256(
        canonical_json({"tree_size": 3, "root_hash": "ab" * 32}).encode()
    ).digest()
    assert _head_digest(_head()) == expected
    # a different root -> a different imprint
    assert _head_digest({"tree_size": 3, "root_hash": "cd" * 32}) != expected


# ---------------------------------------------------------------------------
# anchor_head end-to-end with an injected POST (no network)
# ---------------------------------------------------------------------------
def test_anchor_head_returns_a_token(monkeypatch):
    head = _head()

    def fake_post(url, tsq_bytes, timeout):
        # a stand-in TSA that echoes a token; real parsing is tested separately
        assert url == "https://tsa.example/tsr"
        assert isinstance(tsq_bytes, bytes)
        return b"\x30\x03TSR-TOKEN-BYTES"  # opaque DER-ish blob

    token = anchor_head(head, tsa_url="https://tsa.example/tsr", _post=fake_post)
    assert token["tsa_url"] == "https://tsa.example/tsr"
    assert token["tree_size"] == 3
    assert token["root_hash"] == "ab" * 32
    assert token["message_imprint_sha256"]     # the hash that was timestamped
    assert token["timestamp_token_b64"]         # the TSA's signed token, stored


def test_anchor_head_network_failure_is_wrapped(monkeypatch):
    def boom(url, tsq_bytes, timeout):
        raise OSError("connection refused")

    with pytest.raises(TimestampError, match="could not reach TSA|connection"):
        anchor_head(_head(), tsa_url="https://tsa.example/tsr", _post=boom)


def test_anchor_head_rejects_empty_response():
    with pytest.raises(TimestampError, match="empty|no token"):
        anchor_head(_head(), tsa_url="https://tsa.example/tsr",
                    _post=lambda u, q, timeout: b"")


# ---------------------------------------------------------------------------
# the stored token records what an auditor needs to re-verify later
# ---------------------------------------------------------------------------
def test_token_is_self_describing_for_reverification():
    token = anchor_head(_head(), tsa_url="https://tsa.example/tsr",
                        _post=lambda u, q, timeout: b"TOKEN")
    # everything an auditor needs to re-check the timestamp offline later:
    for key in ("tsa_url", "tree_size", "root_hash",
                "message_imprint_sha256", "timestamp_token_b64", "hash_alg"):
        assert key in token
    assert token["hash_alg"] == "sha256"


# ---------------------------------------------------------------------------
# DER encoder edge cases (long-form length, big nonce) — correctness matters
# because a malformed request is silently rejected by real TSAs
# ---------------------------------------------------------------------------
def test_der_handles_long_form_length_and_large_nonce():
    from abom.anchor import build_tsq
    # a large 64-bit nonce exercises multi-byte INTEGER + long-form length paths
    tsq, nonce = build_tsq(_head(), nonce=2**63 + 12345)
    assert tsq[0] == 0x30                      # SEQUENCE
    # outer length is long-form (>127 bytes payload not required, but structure valid)
    # re-parse top-level to confirm total length is self-consistent
    def tlv(b, i=0):
        tag = b[i]; i += 1
        n = b[i]; i += 1
        if n & 0x80:
            k = n & 0x7f; n = int.from_bytes(b[i:i+k], "big"); i += k
        return tag, n, i
    tag, length, off = tlv(tsq)
    assert tag == 0x30 and length + off == len(tsq)


def test_der_len_long_form():
    from abom.anchor import _der_len
    assert _der_len(0x7f) == b"\x7f"           # short form boundary
    assert _der_len(0x80) == b"\x81\x80"       # long form, 1 length byte
    assert _der_len(256) == b"\x82\x01\x00"    # long form, 2 length bytes


def test_der_int_pads_high_bit():
    from abom.anchor import _der_int
    # a value whose top byte has the high bit set must be zero-padded to stay positive
    enc = _der_int(0x80)
    assert enc == b"\x02\x02\x00\x80"
