"""Offline license token validation using Ed25519 signature verification.

Offline tokens are generated by the LicensesOS API during validation when
the policy allows offline validation. They contain a signed snapshot of
the license state that can be verified without making an API call.

Token format: ``base64url(JSON_payload).base64url(Ed25519_signature)``

Requires the ``cryptography`` package. Install with::

    pip install licenseos[offline]
"""

from __future__ import annotations

import base64
import json
import time
from typing import Any

from licensesos.results.offline_validation import OfflineValidationResult


def _ensure_cryptography() -> Any:
    """Import and return the cryptography Ed25519 public key class.

    Raises a clear error if the ``cryptography`` package is not installed.
    """
    try:
        from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey

        return Ed25519PublicKey
    except ImportError:
        raise ImportError(
            'The "cryptography" package is required for offline validation. '
            "Install it with: pip install licenseos[offline]"
        ) from None


def _base64url_decode(data: str) -> bytes:
    """Decode a base64url-encoded string (with or without padding)."""
    # Add padding if needed
    padding = 4 - len(data) % 4
    if padding != 4:
        data += "=" * padding
    return base64.urlsafe_b64decode(data)


class OfflineValidator:
    """Validates offline license tokens using Ed25519 signature verification.

    Args:
        public_key_base64: The app's Ed25519 public key, base64-encoded
            (raw 32-byte key).
    """

    def __init__(self, public_key_base64: str) -> None:
        Ed25519PublicKey = _ensure_cryptography()

        raw_key_bytes = base64.b64decode(public_key_base64)
        # Python's cryptography library accepts raw 32-byte Ed25519 keys
        # directly via from_public_bytes() -- no SPKI wrapping needed.
        self._public_key = Ed25519PublicKey.from_public_bytes(raw_key_bytes)

    def validate(self, token: str) -> OfflineValidationResult:
        """Validate an offline token.

        Args:
            token: The offline token string
                (``base64url(payload).base64url(signature)``).

        Returns:
            An ``OfflineValidationResult`` indicating validity and decoded
            license data.
        """
        from cryptography.exceptions import InvalidSignature

        parts = token.split(".")
        if len(parts) != 2:
            return OfflineValidationResult.invalid("INVALID_TOKEN_FORMAT")

        try:
            payload_bytes = _base64url_decode(parts[0])
            signature_bytes = _base64url_decode(parts[1])
        except Exception:
            return OfflineValidationResult.invalid("INVALID_TOKEN_FORMAT")

        # Verify the Ed25519 signature
        try:
            self._public_key.verify(signature_bytes, payload_bytes)
        except InvalidSignature:
            return OfflineValidationResult.invalid("INVALID_SIGNATURE")
        except Exception:
            return OfflineValidationResult.invalid("INVALID_SIGNATURE")

        # Parse the JSON payload
        try:
            payload: dict[str, Any] = json.loads(payload_bytes.decode("utf-8"))
        except (json.JSONDecodeError, UnicodeDecodeError):
            return OfflineValidationResult.invalid("INVALID_PAYLOAD")

        # Check expiry
        now = int(time.time())
        if payload.get("exp", 0) < now:
            return OfflineValidationResult.expired(payload)

        return OfflineValidationResult.from_payload(payload, valid=True)
