"""Algorithm compatibility matrix between ACT and ECT.

Facts pinned here:
  - Both specs share the JWS/ES256 signing primitive.
  - ECT is ES256-only; ACT also supports EdDSA.
  - ECT's typ gate rejects cross-type compact tokens even when the
    signature algorithm matches — that separation is deliberate.
"""

from __future__ import annotations

import pytest

from act.errors import ACTValidationError
from ect.verify import verify as ect_verify, VerifyOptions


class TestAlgorithmMatrix:
    def test_es256_act_record_signature_primitive_verifies(
        self, act_record_builder, dual_resolver
    ):
        """ACT Record signed ES256 feeds through ECT verify up to the typ gate.

        Signature primitive is wire-compatible; typ=act+jwt causes
        ECT to refuse. Documents "sig-compatible, typ-rejected".
        """
        _, ect_resolver = dual_resolver
        compact, _ = act_record_builder(alg="ES256")

        opts = VerifyOptions(resolve_key=ect_resolver)
        with pytest.raises(ValueError, match="invalid typ parameter"):
            ect_verify(compact, opts)

    def test_eddsa_act_record_rejected_by_ect(
        self, act_record_builder, ed25519_keypair
    ):
        """ACT Record signed EdDSA — ECT parser rejects at alg gate."""
        priv, _ = ed25519_keypair
        compact, _ = act_record_builder(alg="EdDSA", priv=priv)

        # ect.parse (used indirectly via verify) rejects non-ES256.
        # verify() checks typ first, but parse() would reject alg first;
        # either way the token is rejected — record which gate fires.
        opts = VerifyOptions(resolve_key=lambda kid: None)
        with pytest.raises(ValueError) as exc:
            ect_verify(compact, opts)
        # The verify() path checks typ before alg. We accept either
        # error message as valid "rejection by ECT" evidence.
        assert (
            "invalid typ parameter" in str(exc.value)
            or "expected ES256" in str(exc.value)
        )

    def test_ect_compact_rejected_by_act_decoder(self, ect_payload_builder):
        """Mirror direction: ECT compact → ACT decoder rejects on typ."""
        from act.token import decode_jws

        compact, _ = ect_payload_builder()
        with pytest.raises(ACTValidationError, match="typ"):
            decode_jws(compact)

    def test_es256_primitive_is_wire_compatible_at_raw_sig_level(
        self, ect_payload_builder, es256_keypair
    ):
        """The raw ES256 signature over an ECT compact verifies with
        act.crypto.verify using the ECT public key.

        This is the layer that is actually portable: both refimpls
        sign/verify over the same RFC 7518 §3.4 raw r||s format.
        """
        from act.crypto import verify as act_verify
        from act.token import _b64url_decode

        priv, pub = es256_keypair
        compact, _ = ect_payload_builder(priv=priv)
        parts = compact.split(".")
        signing_input = f"{parts[0]}.{parts[1]}".encode("ascii")
        signature = _b64url_decode(parts[2])

        # Should not raise.
        act_verify(pub, signature, signing_input)


def test_compatibility_matrix_assertion(
    act_record_builder, ed25519_keypair, dual_resolver
):
    """Pin the whole matrix in one assertion block.

    Matrix entries:
      (ACT/ES256) × ECT-verify  -> sig-compatible-but-typ-rejected
      (ACT/EdDSA) × ECT-verify  -> rejected (alg or typ)
    """
    _, ect_resolver = dual_resolver
    opts = VerifyOptions(resolve_key=ect_resolver)

    # ACT/ES256 × ECT-verify
    act_es256, _ = act_record_builder(alg="ES256")
    with pytest.raises(ValueError, match="invalid typ parameter"):
        ect_verify(act_es256, opts)

    # ACT/EdDSA × ECT-verify
    priv, _ = ed25519_keypair
    act_eddsa, _ = act_record_builder(alg="EdDSA", priv=priv)
    with pytest.raises(ValueError):
        ect_verify(act_eddsa, opts)
