"""Typed licensing failures shared by storage, verification, and policy."""

from __future__ import annotations

from enum import Enum
from pathlib import Path
from typing import Optional


class LicenseErrorCode(str, Enum):
    INVALID_ARGUMENT = "invalid_argument"
    INVALID_DOCUMENT = "invalid_document"
    DOCUMENT_TOO_LARGE = "document_too_large"
    UNSUPPORTED_SCHEMA = "unsupported_schema"
    DUPLICATE_JSON_KEY = "duplicate_json_key"
    NON_CANONICAL_VALUE = "non_canonical_value"
    UNKNOWN_SIGNING_KEY = "unknown_signing_key"
    INVALID_SIGNATURE = "invalid_signature"
    KEY_NOT_YET_VALID = "key_not_yet_valid"
    KEY_EXPIRED = "key_expired"
    CLAIM_NOT_YET_VALID = "claim_not_yet_valid"
    WRONG_PRODUCT = "wrong_product"
    WRONG_DEVICE = "wrong_device"
    WRONG_APPLICATION_VERSION = "wrong_application_version"
    STORAGE_NOT_FOUND = "storage_not_found"
    STORAGE_IO_ERROR = "storage_io_error"
    SECURE_STORAGE_UNAVAILABLE = "secure_storage_unavailable"
    CATALOG_INVALID = "catalog_invalid"
    UNKNOWN_ENTITLEMENT = "unknown_entitlement"


class LicensingError(RuntimeError):
    """A bounded licensing failure with a stable machine-readable code."""

    def __init__(
        self,
        code: LicenseErrorCode,
        message: str,
        *,
        path: Optional[str | Path] = None,
    ) -> None:
        if not isinstance(code, LicenseErrorCode):
            raise TypeError("code must be a LicenseErrorCode")
        if not isinstance(message, str):
            raise TypeError("message must be a string")
        normalized = message.strip()
        if not normalized:
            raise ValueError("message must not be empty")
        super().__init__(normalized)
        self.code = code
        self.path = None if path is None else Path(path)


__all__ = ["LicenseErrorCode", "LicensingError"]
