"""Compact JWT signing and verification using ECDSA P-256 (ES256)."""

from __future__ import annotations

import json
from dataclasses import dataclass
from typing import Any

from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature, encode_dss_signature
from cryptography.hazmat.primitives.serialization import load_der_private_key, load_der_public_key

from hallpass_sdk.keys import _base64url_decode, _base64url_encode, _stable_json


@dataclass
class VerifyJwtResult:
    valid: bool
    payload: dict[str, Any] | None
    header: dict[str, Any] | None


def _jwk_to_ec_private_key(jwk: dict[str, Any]) -> ec.EllipticCurvePrivateKey:
    d = int.from_bytes(_base64url_decode(jwk["d"]), "big")
    x = int.from_bytes(_base64url_decode(jwk["x"]), "big")
    y = int.from_bytes(_base64url_decode(jwk["y"]), "big")
    public_numbers = ec.EllipticCurvePublicNumbers(x, y, ec.SECP256R1())
    private_numbers = ec.EllipticCurvePrivateNumbers(d, public_numbers)
    return private_numbers.private_key()


def _jwk_to_ec_public_key(jwk: dict[str, Any]) -> ec.EllipticCurvePublicKey:
    x = int.from_bytes(_base64url_decode(jwk["x"]), "big")
    y = int.from_bytes(_base64url_decode(jwk["y"]), "big")
    public_numbers = ec.EllipticCurvePublicNumbers(x, y, ec.SECP256R1())
    return public_numbers.public_key()


def _encode_json(value: dict[str, Any]) -> str:
    return _base64url_encode(_stable_json(value).encode("utf-8"))


def _decode_json(value: str) -> dict[str, Any]:
    return json.loads(_base64url_decode(value))


def _sign_raw(data: bytes, private_key: ec.EllipticCurvePrivateKey) -> bytes:
    """Sign with ECDSA and return IEEE P1363 (r || s) signature."""
    der_sig = private_key.sign(data, ec.ECDSA(hashes.SHA256()))
    r, s = decode_dss_signature(der_sig)
    byte_len = 32  # P-256
    return r.to_bytes(byte_len, "big") + s.to_bytes(byte_len, "big")


def _verify_raw(data: bytes, signature: bytes, public_key: ec.EllipticCurvePublicKey) -> bool:
    """Verify an IEEE P1363 (r || s) ECDSA signature."""
    byte_len = 32
    r = int.from_bytes(signature[:byte_len], "big")
    s = int.from_bytes(signature[byte_len:], "big")
    der_sig = encode_dss_signature(r, s)
    try:
        public_key.verify(der_sig, data, ec.ECDSA(hashes.SHA256()))
        return True
    except Exception:
        return False


def sign_compact_jwt(
    payload: dict[str, Any],
    private_jwk: dict[str, Any],
    header: dict[str, Any] | None = None,
) -> str:
    """Sign a compact JWT with ES256."""
    complete_header: dict[str, Any] = {"alg": "ES256", "typ": "JWT"}
    if header:
        complete_header.update(header)

    encoded_header = _encode_json(complete_header)
    encoded_payload = _encode_json(payload)
    signing_input = f"{encoded_header}.{encoded_payload}"

    private_key = _jwk_to_ec_private_key(private_jwk)
    signature = _sign_raw(signing_input.encode("utf-8"), private_key)

    return f"{signing_input}.{_base64url_encode(signature)}"


def verify_compact_jwt(compact_jwt: str, public_jwk: dict[str, Any]) -> VerifyJwtResult:
    """Verify a compact JWT signature and return the decoded contents."""
    parts = compact_jwt.split(".")
    if len(parts) != 3:
        return VerifyJwtResult(valid=False, payload=None, header=None)

    signing_input = f"{parts[0]}.{parts[1]}"
    signature = _base64url_decode(parts[2])
    public_key = _jwk_to_ec_public_key(public_jwk)

    valid = _verify_raw(signing_input.encode("utf-8"), signature, public_key)

    return VerifyJwtResult(
        valid=valid,
        header=_decode_json(parts[0]),
        payload=_decode_json(parts[1]),
    )


def decode_compact_jwt(compact_jwt: str) -> VerifyJwtResult:
    """Decode a JWT without verifying the signature."""
    parts = compact_jwt.split(".")
    if len(parts) != 3:
        return VerifyJwtResult(valid=False, payload=None, header=None)

    return VerifyJwtResult(
        valid=True,
        header=_decode_json(parts[0]),
        payload=_decode_json(parts[1]),
    )
