"""Reference verifier for MIZAN receipts (Python). Stdlib + cryptography + coincurve + eth_account.
Used by the conformance runner; mirrors MizanProof.sol and the mizan-onchain crate exactly."""
import hashlib, struct

SKEW = 300
HALF_N = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0


def parse_compact(b: bytes):
    if len(b) < 6 or b[:4] != b"MZN1":
        raise ValueError("BadMagic")
    n = b[5]
    if len(b) != 6 + n + 32 + 16:
        raise ValueError("Truncated")
    iat, exp = struct.unpack(">QQ", b[6 + n + 32:])
    return {"kind": b[4], "subject": b[6:6 + n], "credential": b[6 + n:6 + n + 32], "iat": iat, "exp": exp}


def _time(iat, exp, now):
    if now > exp:
        raise ValueError("Expired")
    if iat > now + SKEW:
        raise ValueError("NotYetValid")


def verify_compact(b: bytes, sig: bytes, alg: str, pub: bytes, kind: int, subject: bytes, now: int):
    r = parse_compact(b)
    if r["kind"] != kind:
        raise ValueError("WrongKind")
    if r["subject"] != subject:
        raise ValueError("WrongSubject")
    _time(r["iat"], r["exp"], now)
    if alg == "ed25519":
        from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
        try:
            if len(sig) != 64:
                raise ValueError
            Ed25519PublicKey.from_public_bytes(pub).verify(sig, b)
        except Exception:
            raise ValueError("BadSignature")
    else:
        if len(sig) != 64 or int.from_bytes(sig[32:], "big") > HALF_N:
            raise ValueError("BadSignature")
        r_, s_ = int.from_bytes(sig[:32], "big"), int.from_bytes(sig[32:], "big")
        from cryptography.hazmat.primitives.asymmetric import ec
        from cryptography.hazmat.primitives.asymmetric.utils import encode_dss_signature, Prehashed
        from cryptography.hazmat.primitives import hashes
        try:
            ec.EllipticCurvePublicKey.from_encoded_point(ec.SECP256K1(), pub).verify(
                encode_dss_signature(r_, s_), hashlib.sha256(b).digest(), ec.ECDSA(Prehashed(hashes.SHA256())))
        except Exception:
            raise ValueError("BadSignature")
    return r


def verify_eip712(receipt: dict, sig: bytes, signer: str, subject: str, now: int):
    from eth_account import Account
    from eth_account.messages import encode_typed_data
    if receipt["subject"].lower() != subject.lower() or int(subject, 16) == 0:
        raise ValueError("WrongSubject")
    _time(receipt["issuedAt"], receipt["expiresAt"], now)
    if len(sig) != 65 or int.from_bytes(sig[32:64], "big") > HALF_N:
        raise ValueError("BadSignature")
    typed = {"types": {"EIP712Domain": [{"name": "name", "type": "string"}, {"name": "version", "type": "string"}],
                       "Receipt": [{"name": "subject", "type": "address"}, {"name": "credential", "type": "bytes32"},
                                   {"name": "issuedAt", "type": "uint64"}, {"name": "expiresAt", "type": "uint64"}]},
             "primaryType": "Receipt", "domain": {"name": "MIZAN", "version": "1"}, "message": receipt}
    try:
        got = Account.recover_message(encode_typed_data(full_message=typed), signature=sig)
    except Exception:
        raise ValueError("BadSignature")
    if got.lower() != signer.lower():
        raise ValueError("BadSignature")
    return receipt


# ---------------------------------------------------------------- v2
def parse_compact2(b: bytes):
    if len(b) < 6 or b[:4] != b"MZN2":
        raise ValueError("BadMagic")
    n = b[5]
    if len(b) != 6 + n + 32 + 32 + 2 + 32 + 1 + 16:
        raise ValueError("Truncated")
    i = 6 + n
    cred, prof = b[i:i + 32], b[i + 32:i + 64]; i += 64
    rung, prov = b[i], b[i + 1]; i += 2
    req = b[i:i + 32]; i += 32
    if b[i] not in (0, 1):
        raise ValueError("Truncated")
    met = b[i] == 1; i += 1
    iat, exp = struct.unpack(">QQ", b[i:i + 16])
    return {"kind": b[4], "subject": b[6:6 + n], "credential": cred, "profile": prof, "rung": rung, "provenance": prov,
            "requirement": req, "met": met, "iat": iat, "exp": exp}


def verify_compact2(b, sig, alg, pub, kind, subject, requirement: bytes, min_rung, now):
    r = parse_compact2(b)
    if r["kind"] != kind:
        raise ValueError("WrongKind")
    if r["subject"] != subject:
        raise ValueError("WrongSubject")
    if not r["met"] or r["requirement"] != requirement or r["rung"] < min_rung:
        raise ValueError("NotMet")
    _time(r["iat"], r["exp"], now)
    _sigcheck(b, sig, alg, pub)
    return r


def _sigcheck(b, sig, alg, pub):
    if alg == "ed25519":
        from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
        try:
            if len(sig) != 64:
                raise ValueError
            Ed25519PublicKey.from_public_bytes(pub).verify(sig, b)
        except Exception:
            raise ValueError("BadSignature")
    else:
        from cryptography.hazmat.primitives.asymmetric import ec
        from cryptography.hazmat.primitives.asymmetric.utils import encode_dss_signature, Prehashed
        from cryptography.hazmat.primitives import hashes
        if len(sig) != 64 or int.from_bytes(sig[32:], "big") > HALF_N:
            raise ValueError("BadSignature")
        try:
            ec.EllipticCurvePublicKey.from_encoded_point(ec.SECP256K1(), pub).verify(
                encode_dss_signature(int.from_bytes(sig[:32], "big"), int.from_bytes(sig[32:], "big")), hashlib.sha256(b).digest(), ec.ECDSA(Prehashed(hashes.SHA256())))
        except Exception:
            raise ValueError("BadSignature")


def verify_eip712v2(receipt: dict, sig: bytes, signer: str, subject: str, requirement_hex: str, min_rung: int, now: int):
    from eth_account import Account
    from eth_account.messages import encode_typed_data
    if receipt["subject"].lower() != subject.lower() or int(subject, 16) == 0:
        raise ValueError("WrongSubject")
    if not receipt["met"] or receipt["requirement"].lower().replace("0x", "") != requirement_hex.lower() or receipt["rung"] < min_rung:
        raise ValueError("NotMet")
    _time(receipt["issuedAt"], receipt["expiresAt"], now)
    if len(sig) != 65 or int.from_bytes(sig[32:64], "big") > HALF_N:
        raise ValueError("BadSignature")
    T = {"EIP712Domain": [{"name": "name", "type": "string"}, {"name": "version", "type": "string"}],
         "ReceiptV2": [{"name": "subject", "type": "address"}, {"name": "credential", "type": "bytes32"}, {"name": "profile", "type": "bytes32"},
                       {"name": "rung", "type": "uint8"}, {"name": "provenance", "type": "uint8"}, {"name": "requirement", "type": "bytes32"},
                       {"name": "met", "type": "bool"}, {"name": "issuedAt", "type": "uint64"}, {"name": "expiresAt", "type": "uint64"}]}
    try:
        got = Account.recover_message(encode_typed_data(full_message={"types": T, "primaryType": "ReceiptV2", "domain": {"name": "MIZAN", "version": "2"}, "message": receipt}), signature=sig)
    except Exception:
        raise ValueError("BadSignature")
    if got.lower() != signer.lower():
        raise ValueError("BadSignature")
    return receipt


# ---------------------------------------------------------------- v3: also names who the subject acts for
Z32 = b"\x00" * 32


def parse_compact3(b: bytes):
    if len(b) < 6 or b[:4] != b"MZN3":
        raise ValueError("BadMagic")
    n = b[5]
    if len(b) != 6 + n + 32 + 32 + 2 + 32 + 1 + 32 + 32 + 16:
        raise ValueError("Truncated")
    i = 6 + n
    cred, prof = b[i:i + 32], b[i + 32:i + 64]; i += 64
    rung, prov = b[i], b[i + 1]; i += 2
    req = b[i:i + 32]; i += 32
    if b[i] not in (0, 1):
        raise ValueError("Truncated")
    met = b[i] == 1; i += 1
    prin, auth = b[i:i + 32], b[i + 32:i + 64]; i += 64
    iat, exp = struct.unpack(">QQ", b[i:i + 16])
    return {"kind": b[4], "subject": b[6:6 + n], "credential": cred, "profile": prof, "rung": rung, "provenance": prov,
            "requirement": req, "met": met, "principal": prin, "authority": auth, "iat": iat, "exp": exp}


def verify_compact3(b, sig, alg, pub, kind, subject, requirement: bytes, min_rung, principal: bytes, now):
    r = parse_compact3(b)
    if r["kind"] != kind:
        raise ValueError("WrongKind")
    if r["subject"] != subject:
        raise ValueError("WrongSubject")
    if not r["met"] or r["requirement"] != requirement or r["rung"] < min_rung:
        raise ValueError("NotMet")
    if principal == Z32 or r["principal"] != principal or r["authority"] == Z32:
        raise ValueError("NotAuthorized")
    _time(r["iat"], r["exp"], now)
    _sigcheck(b, sig, alg, pub)
    return r


def verify_eip712v3(receipt: dict, sig: bytes, signer: str, subject: str, requirement_hex: str, min_rung: int, principal_hex: str, now: int):
    from eth_account import Account
    from eth_account.messages import encode_typed_data
    if receipt["subject"].lower() != subject.lower() or int(subject, 16) == 0:
        raise ValueError("WrongSubject")
    if not receipt["met"] or receipt["requirement"].lower().replace("0x", "") != requirement_hex.lower() or receipt["rung"] < min_rung:
        raise ValueError("NotMet")
    p = receipt["principal"].lower().replace("0x", "")
    if int(principal_hex, 16) == 0 or p != principal_hex.lower() or int(receipt["authority"], 16) == 0:
        raise ValueError("NotAuthorized")
    _time(receipt["issuedAt"], receipt["expiresAt"], now)
    if len(sig) != 65 or int.from_bytes(sig[32:64], "big") > HALF_N:
        raise ValueError("BadSignature")
    T = {"EIP712Domain": [{"name": "name", "type": "string"}, {"name": "version", "type": "string"}],
         "ReceiptV3": [{"name": "subject", "type": "address"}, {"name": "credential", "type": "bytes32"}, {"name": "profile", "type": "bytes32"},
                       {"name": "rung", "type": "uint8"}, {"name": "provenance", "type": "uint8"}, {"name": "requirement", "type": "bytes32"},
                       {"name": "met", "type": "bool"}, {"name": "principal", "type": "bytes32"}, {"name": "authority", "type": "bytes32"},
                       {"name": "issuedAt", "type": "uint64"}, {"name": "expiresAt", "type": "uint64"}]}
    try:
        got = Account.recover_message(encode_typed_data(full_message={"types": T, "primaryType": "ReceiptV3", "domain": {"name": "MIZAN", "version": "3"}, "message": receipt}), signature=sig)
    except Exception:
        raise ValueError("BadSignature")
    if got.lower() != signer.lower():
        raise ValueError("BadSignature")
    return receipt
