"""
Credential issuer — creates and signs identity credentials.

In production, this runs on the KYC provider's infrastructure (e.g. Onfido,
Jumio, Veriff). The issuer verifies a real identity document, then issues
a signed credential containing only the claims needed — never the raw data.
"""

from __future__ import annotations

import base64
import hashlib
import json
import logging
from dataclasses import dataclass, field, asdict
from datetime import datetime, timezone
from typing import Any

from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives import serialization

from .exceptions import CredentialError
from .storage import FileStorage

logger = logging.getLogger(__name__)

# Supported claim types
SUPPORTED_AGE_THRESHOLDS = (13, 16, 18, 21, 25)


@dataclass(frozen=True)
class Claims:
    """The set of verifiable claims embedded in a credential."""
    age_over_13: bool = False
    age_over_16: bool = False
    age_over_18: bool = False
    age_over_21: bool = False
    age_over_25: bool = False
    country: str = ""

    def to_dict(self) -> dict[str, Any]:
        return asdict(self)


@dataclass(frozen=True)
class Credential:
    """A signed identity credential."""
    issuer: str
    issued_at: str
    subject_id: str
    claims: dict[str, Any]
    signature: str

    def to_dict(self) -> dict[str, Any]:
        return {
            "credential": {
                "issuer": self.issuer,
                "issued_at": self.issued_at,
                "subject_id": self.subject_id,
                "claims": self.claims,
            },
            "signature": self.signature,
        }


class Issuer:
    """
    Issues signed credentials after identity verification.

    Usage:
        issuer = Issuer(storage=FileStorage("/path/to/issuer/keys"))
        issuer.generate_keys()  # once
        credential = issuer.issue(user_id="u001", age=22, country="GB")
    """

    def __init__(
        self,
        storage: FileStorage,
        issuer_name: str = "privacy-creds-issuer",
    ):
        self.storage = storage
        self.issuer_name = issuer_name

    def generate_keys(self) -> None:
        """Generate and store the issuer's Ed25519 signing keypair."""
        private_key = Ed25519PrivateKey.generate()
        public_key = private_key.public_key()

        priv_pem = private_key.private_bytes(
            serialization.Encoding.PEM,
            serialization.PrivateFormat.PKCS8,
            serialization.NoEncryption(),
        )
        pub_pem = public_key.public_bytes(
            serialization.Encoding.PEM,
            serialization.PublicFormat.SubjectPublicKeyInfo,
        )

        self.storage.write_bytes("issuer_private.pem", priv_pem, secret=True)
        self.storage.write_bytes("issuer_public.pem", pub_pem, secret=False)
        logger.info("Issuer keypair generated")

    def _load_private_key(self) -> Ed25519PrivateKey:
        pem = self.storage.read_bytes("issuer_private.pem")
        loaded = serialization.load_pem_private_key(pem, password=None)
        # Extract raw bytes to construct Ed25519 key
        raw = loaded.private_bytes(
            serialization.Encoding.Raw,
            serialization.PrivateFormat.Raw,
            serialization.NoEncryption(),
        )
        return Ed25519PrivateKey.from_private_bytes(raw)

    def get_public_key_pem(self) -> bytes:
        """Return the issuer's public key in PEM format."""
        return self.storage.read_bytes("issuer_public.pem")

    def issue(
        self,
        user_id: str,
        age: int,
        country: str,
    ) -> Credential:
        """
        Issue a signed credential for a verified user.

        Args:
            user_id: Unique identifier for the subject.
            age: Verified age of the subject.
            country: ISO 3166-1 alpha-2 country code.

        Returns:
            A signed Credential object.

        Raises:
            CredentialError: If age or country is invalid.
        """
        if not isinstance(age, int) or age < 0 or age > 150:
            raise CredentialError(f"Invalid age: {age}")
        if not country or len(country) != 2:
            raise CredentialError(f"Country must be a 2-letter ISO code, got: {country!r}")

        country = country.upper()

        # Build claims — boolean age thresholds, never the exact age
        claims = Claims(
            age_over_13=age >= 13,
            age_over_16=age >= 16,
            age_over_18=age >= 18,
            age_over_21=age >= 21,
            age_over_25=age >= 25,
            country=country,
        )

        credential_body = {
            "issuer": self.issuer_name,
            "issued_at": datetime.now(timezone.utc).isoformat(),
            "subject_id": user_id,
            "claims": claims.to_dict(),
        }

        # Sign
        private_key = self._load_private_key()
        canonical = json.dumps(credential_body, sort_keys=True).encode()
        signature = private_key.sign(canonical)
        signature_b64 = base64.b64encode(signature).decode()

        credential = Credential(
            issuer=credential_body["issuer"],
            issued_at=credential_body["issued_at"],
            subject_id=credential_body["subject_id"],
            claims=claims.to_dict(),
            signature=signature_b64,
        )

        logger.info("Credential issued for user %s", user_id)
        return credential
