"""Encryption key rotation for metadata and tags.

Uses ``certisigma.crypto.rotate_key()`` from SDK v1.6.0+ to
re-encrypt metadata envelopes under a new AES-256 key without
exposing plaintext to the server.

NIST SP 800-57 recommends periodic key rotation.  This module
supports rotating the key for a single attestation's metadata,
reading the current encrypted payload, re-encrypting, and
pushing the updated envelope.
"""

from __future__ import annotations

import re
from dataclasses import dataclass
from typing import Any, Optional


@dataclass
class RotationResult:
    attestation_id: str
    success: bool
    rotated_metadata: bool = False
    rotated_tags: int = 0
    error: Optional[str] = None


def rotate_metadata_key(
    attestation_id: str,
    old_key: str,
    new_key: str,
    *,
    api_key: str | None = None,
    base_url: str | None = None,
) -> RotationResult:
    """Rotate the encryption key for an attestation's metadata.

    1. Fetch current metadata via ``get_metadata()``
    2. Re-encrypt via ``crypto.rotate_key()``
    3. Push updated envelope via ``update_metadata()``
    """
    from certisigma import CertiSigmaClient, CertiSigmaError
    from certisigma.crypto import is_encrypted, rotate_key

    _validate_hex_key(old_key, "old_key")
    _validate_hex_key(new_key, "new_key")

    if old_key == new_key:
        return RotationResult(
            attestation_id=attestation_id,
            success=False,
            error="Old and new keys are identical.",
        )

    kwargs: dict[str, Any] = {}
    if api_key:
        kwargs["api_key"] = api_key
    if base_url:
        kwargs["base_url"] = base_url
    client = CertiSigmaClient(**kwargs)
    try:
        rotated_metadata = False
        rotated_tags = 0

        try:
            meta = client.get_metadata(attestation_id)
            if meta.extra_data and is_encrypted(meta.extra_data):
                new_envelope = rotate_key(meta.extra_data, old_key, new_key)
                client.update_metadata(
                    attestation_id,
                    extra_data=new_envelope,
                    client_encrypted=True,
                )
                rotated_metadata = True
        except CertiSigmaError as exc:
            return RotationResult(
                attestation_id=attestation_id,
                success=False,
                error=f"Metadata rotation failed: {exc}",
            )
        except (ValueError, TypeError, KeyError) as exc:
            return RotationResult(
                attestation_id=attestation_id,
                success=False,
                error=f"Key rotation crypto error: {exc}",
            )

        try:
            tags = client.get_tags(attestation_id)
            rotated_tag_list: list[dict[str, Any]] = []
            for t in tags:
                if t.client_encrypted and t.value_enc and t.value_nonce:
                    envelope = {
                        "ciphertext": t.value_enc,
                        "nonce": t.value_nonce,
                        "v": 1,
                    }
                    new_envelope = rotate_key(envelope, old_key, new_key)
                    rotated_tag_list.append({
                        "key": t.key,
                        "value_enc": new_envelope["ciphertext"],
                        "value_nonce": new_envelope["nonce"],
                        "client_encrypted": True,
                    })
            if rotated_tag_list:
                client.put_tags(attestation_id, tags=rotated_tag_list)
                rotated_tags = len(rotated_tag_list)
        except CertiSigmaError as exc:
            _warn = ""
            if rotated_metadata:
                _warn = (
                    " IMPORTANT: metadata was rotated to the new key but"
                    f" {rotated_tags} of {len(rotated_tag_list) if 'rotated_tag_list' in dir() else '?'}"
                    " tags remain under the old key. Do NOT discard the old key."
                )
            return RotationResult(
                attestation_id=attestation_id,
                success=False,
                rotated_metadata=rotated_metadata,
                rotated_tags=rotated_tags,
                error=f"Tag rotation failed: {exc}.{_warn}",
            )
        except (ValueError, TypeError, KeyError) as exc:
            _warn = ""
            if rotated_metadata:
                _warn = (
                    " IMPORTANT: metadata was rotated to the new key."
                    " Do NOT discard the old key until tags are re-rotated."
                )
            return RotationResult(
                attestation_id=attestation_id,
                success=False,
                rotated_metadata=rotated_metadata,
                rotated_tags=rotated_tags,
                error=f"Tag rotation crypto error: {exc}.{_warn}",
            )

        return RotationResult(
            attestation_id=attestation_id,
            success=True,
            rotated_metadata=rotated_metadata,
            rotated_tags=rotated_tags,
        )
    finally:
        client.close()


def _validate_hex_key(key: str, name: str) -> None:
    """Validate a hex-encoded AES-256 key (64 hex chars).

    Uses strict regex — ``int(key, 16)`` accepts underscores and
    leading/trailing whitespace; ``bytes.fromhex()`` silently strips
    whitespace.  Both produce keys shorter than the required 256 bits.
    """
    if not re.fullmatch(r"[0-9a-fA-F]{64}", key):
        raise ValueError(f"{name} must be exactly 64 hex characters.")
