"""Versioned bounded API contracts."""

from __future__ import annotations

import base64
from typing import Any, Optional

from pydantic import BaseModel, ConfigDict, Field, field_validator

from licensing_shared.constants import (
    MAX_IDENTIFIER_LENGTH,
    TRIAL_MAXIMUM_TOTAL_DURATION_HOURS,
    TRIAL_MINIMUM_AUTHORITY_DURATION_HOURS,
)
from licensing_shared.product_surfaces import (
    PRODUCT_SURFACE_KIND_ANALYSIS,
    PRODUCT_SURFACE_KIND_MAIN_TAB,
    product_surface_ids,
)

from .constants import (
    CATALOG_RELEASE_REASONS,
    MAX_FRIENDLY_DEVICE_NAME_CHARACTERS,
    MAX_IDEMPOTENCY_KEY_CHARACTERS,
    MAX_REASON_CHARACTERS,
    MAX_SERIAL_BATCH_QUANTITY,
    OFFLINE_TRIAL_APPROVAL_REASONS,
    OFFLINE_TRIAL_REJECTION_REASONS,
    PRIVACY_DELETION_REASONS,
    SERIAL_BATCH_REVOCATION_REASONS,
    SUPPORT_DEVICE_RECOVERY_REASONS,
    SUPPORT_GRANT_REVOCATION_REASONS,
    SUPPORT_TRIAL_EXTENSION_REASONS,
)


class ApiModel(BaseModel):
    model_config = ConfigDict(extra="forbid", populate_by_name=True)


class DeviceEnrollmentRequest(ApiModel):
    installation_id: str = Field(alias="installationId", min_length=1, max_length=MAX_IDENTIFIER_LENGTH)
    device_public_key: str = Field(alias="devicePublicKey", min_length=1, max_length=2048)
    device_key_thumbprint: str = Field(
        alias="deviceKeyThumbprint",
        min_length=1,
        max_length=MAX_IDENTIFIER_LENGTH,
    )
    key_provider: str = Field(alias="keyProvider", min_length=1, max_length=MAX_IDENTIFIER_LENGTH)
    friendly_name: str = Field(
        alias="friendlyName",
        min_length=1,
        max_length=MAX_FRIENDLY_DEVICE_NAME_CHARACTERS,
    )
    evidence: dict[str, str] = Field(default_factory=dict, max_length=16)

    @field_validator("device_public_key")
    @classmethod
    def validate_public_key_base64(cls, value: str) -> str:
        try:
            decoded = base64.b64decode(value.strip(), validate=True)
        except (TypeError, ValueError) as exc:
            raise ValueError("devicePublicKey must be canonical Base64") from exc
        if not decoded or len(decoded) > 1024:
            raise ValueError("devicePublicKey is empty or too large")
        return value.strip()

    def public_key_der(self) -> bytes:
        return base64.b64decode(self.device_public_key, validate=True)


class DeviceProofRequest(ApiModel):
    schema_name: str = Field(alias="schema", min_length=1, max_length=128)
    schema_version: int = Field(alias="schemaVersion", ge=1)
    algorithm: str = Field(min_length=1, max_length=64)
    action: str = Field(min_length=1, max_length=MAX_IDENTIFIER_LENGTH)
    installation_id: str = Field(alias="installationId", min_length=1, max_length=MAX_IDENTIFIER_LENGTH)
    device_public_key: str = Field(alias="devicePublicKey", min_length=1, max_length=2048)
    device_key_thumbprint: str = Field(
        alias="deviceKeyThumbprint",
        min_length=1,
        max_length=MAX_IDENTIFIER_LENGTH,
    )
    payload_digest: str = Field(alias="payloadDigest", min_length=64, max_length=64)
    created_at: str = Field(alias="createdAt", min_length=1, max_length=64)
    expires_at: str = Field(alias="expiresAt", min_length=1, max_length=64)
    nonce: str = Field(min_length=1, max_length=256)
    signature: str = Field(min_length=1, max_length=256)


class SerialRedemptionRequest(ApiModel):
    serial: str = Field(min_length=1, max_length=128)
    application_major_version: int = Field(alias="applicationMajorVersion", ge=1)
    idempotency_key: str = Field(
        alias="idempotencyKey",
        min_length=1,
        max_length=MAX_IDEMPOTENCY_KEY_CHARACTERS,
    )
    correlation_id: str = Field(alias="correlationId", min_length=1, max_length=MAX_IDENTIFIER_LENGTH)
    target_license_id: Optional[str] = Field(
        default=None,
        alias="targetLicenseId",
        max_length=MAX_IDENTIFIER_LENGTH,
    )
    device: DeviceEnrollmentRequest
    device_proof: DeviceProofRequest = Field(alias="deviceProof")

    def proof_payload(self) -> dict[str, Any]:
        return self.model_dump(by_alias=True, exclude={"device_proof"})


class TrialStartRequest(ApiModel):
    application_major_version: int = Field(alias="applicationMajorVersion", ge=1)
    idempotency_key: str = Field(
        alias="idempotencyKey",
        min_length=1,
        max_length=MAX_IDEMPOTENCY_KEY_CHARACTERS,
    )
    correlation_id: str = Field(alias="correlationId", min_length=1, max_length=MAX_IDENTIFIER_LENGTH)
    device: DeviceEnrollmentRequest
    device_proof: DeviceProofRequest = Field(alias="deviceProof")

    def proof_payload(self) -> dict[str, Any]:
        return self.model_dump(by_alias=True, exclude={"device_proof"})


class ActivationRefreshRequest(ApiModel):
    license_id: str = Field(alias="licenseId", min_length=1, max_length=MAX_IDENTIFIER_LENGTH)
    installation_id: str = Field(alias="installationId", min_length=1, max_length=MAX_IDENTIFIER_LENGTH)
    application_major_version: int = Field(alias="applicationMajorVersion", ge=1)
    correlation_id: str = Field(alias="correlationId", min_length=1, max_length=MAX_IDENTIFIER_LENGTH)
    evidence: dict[str, str] = Field(default_factory=dict, max_length=16)
    device_proof: DeviceProofRequest = Field(alias="deviceProof")

    def proof_payload(self) -> dict[str, Any]:
        payload = self.model_dump(by_alias=True, exclude={"device_proof"})
        if not self.evidence:
            payload.pop("evidence", None)
        return payload


class AccountLicenseActivationRequest(ApiModel):
    application_major_version: int = Field(alias="applicationMajorVersion", ge=1)
    idempotency_key: str = Field(
        alias="idempotencyKey",
        min_length=1,
        max_length=MAX_IDEMPOTENCY_KEY_CHARACTERS,
    )
    correlation_id: str = Field(
        alias="correlationId",
        min_length=1,
        max_length=MAX_IDENTIFIER_LENGTH,
    )
    device: DeviceEnrollmentRequest
    device_proof: DeviceProofRequest = Field(alias="deviceProof")

    def proof_payload(self) -> dict[str, Any]:
        return self.model_dump(by_alias=True, exclude={"device_proof"})


class ActivationDeactivateRequest(ApiModel):
    license_id: str = Field(alias="licenseId", min_length=1, max_length=MAX_IDENTIFIER_LENGTH)
    installation_id: str = Field(alias="installationId", min_length=1, max_length=MAX_IDENTIFIER_LENGTH)
    correlation_id: str = Field(alias="correlationId", min_length=1, max_length=MAX_IDENTIFIER_LENGTH)
    reason: str = Field(default="local_device_release", min_length=1, max_length=MAX_REASON_CHARACTERS)
    idempotency_key: Optional[str] = Field(
        default=None,
        alias="idempotencyKey",
        min_length=1,
        max_length=MAX_IDEMPOTENCY_KEY_CHARACTERS,
    )
    evidence: dict[str, str] = Field(default_factory=dict, max_length=16)
    device_proof: DeviceProofRequest = Field(alias="deviceProof")

    def proof_payload(self) -> dict[str, Any]:
        payload = self.model_dump(
            by_alias=True,
            exclude={"device_proof"},
            exclude_none=True,
        )
        if not self.evidence:
            payload.pop("evidence", None)
        return payload


class SerialClaimRequest(ApiModel):
    serial: str = Field(min_length=1, max_length=128)
    correlation_id: str = Field(
        alias="correlationId",
        min_length=1,
        max_length=MAX_IDENTIFIER_LENGTH,
    )


class RemoteDeviceReleaseRequest(ApiModel):
    correlation_id: str = Field(
        alias="correlationId",
        min_length=1,
        max_length=MAX_IDENTIFIER_LENGTH,
    )
    reason: str = Field(
        default="remote_unavailable_release",
        pattern="^(remote_unavailable_release|marked_lost)$",
    )


class DeviceRenameRequest(ApiModel):
    friendly_name: str = Field(
        alias="friendlyName",
        min_length=1,
        max_length=MAX_FRIENDLY_DEVICE_NAME_CHARACTERS,
    )
    idempotency_key: str = Field(
        alias="idempotencyKey",
        min_length=1,
        max_length=MAX_IDEMPOTENCY_KEY_CHARACTERS,
    )
    correlation_id: str = Field(
        alias="correlationId",
        min_length=1,
        max_length=MAX_IDENTIFIER_LENGTH,
    )


class OrganizationCreateRequest(ApiModel):
    display_name: str = Field(alias="displayName", min_length=1, max_length=256)
    idempotency_key: str = Field(
        alias="idempotencyKey",
        min_length=1,
        max_length=MAX_IDEMPOTENCY_KEY_CHARACTERS,
    )
    correlation_id: str = Field(
        alias="correlationId",
        min_length=1,
        max_length=MAX_IDENTIFIER_LENGTH,
    )


class OrganizationInvitationCreateRequest(ApiModel):
    verified_email: str = Field(alias="verifiedEmail", min_length=3, max_length=256)
    role: str = Field(default="member", pattern="^(member|admin)$")
    idempotency_key: str = Field(
        alias="idempotencyKey",
        min_length=1,
        max_length=MAX_IDEMPOTENCY_KEY_CHARACTERS,
    )
    correlation_id: str = Field(
        alias="correlationId",
        min_length=1,
        max_length=MAX_IDENTIFIER_LENGTH,
    )


class OrganizationInvitationAcceptRequest(ApiModel):
    invitation_token: str = Field(
        alias="invitationToken",
        min_length=1,
        max_length=512,
    )
    idempotency_key: str = Field(
        alias="idempotencyKey",
        min_length=1,
        max_length=MAX_IDEMPOTENCY_KEY_CHARACTERS,
    )
    correlation_id: str = Field(
        alias="correlationId",
        min_length=1,
        max_length=MAX_IDENTIFIER_LENGTH,
    )


class OrganizationLicenseAttachRequest(ApiModel):
    idempotency_key: str = Field(
        alias="idempotencyKey",
        min_length=1,
        max_length=MAX_IDEMPOTENCY_KEY_CHARACTERS,
    )
    correlation_id: str = Field(
        alias="correlationId",
        min_length=1,
        max_length=MAX_IDENTIFIER_LENGTH,
    )


class OrganizationSeatAssignRequest(ApiModel):
    user_id: str = Field(alias="userId", min_length=1, max_length=MAX_IDENTIFIER_LENGTH)
    idempotency_key: str = Field(
        alias="idempotencyKey",
        min_length=1,
        max_length=MAX_IDEMPOTENCY_KEY_CHARACTERS,
    )
    correlation_id: str = Field(
        alias="correlationId",
        min_length=1,
        max_length=MAX_IDENTIFIER_LENGTH,
    )


class OrganizationSeatReleaseRequest(ApiModel):
    idempotency_key: str = Field(
        alias="idempotencyKey",
        min_length=1,
        max_length=MAX_IDEMPOTENCY_KEY_CHARACTERS,
    )
    correlation_id: str = Field(
        alias="correlationId",
        min_length=1,
        max_length=MAX_IDENTIFIER_LENGTH,
    )


class SerialBatchRequest(ApiModel):
    sku_id: str = Field(alias="skuId", min_length=1, max_length=MAX_IDENTIFIER_LENGTH)
    quantity: int = Field(ge=1, le=MAX_SERIAL_BATCH_QUANTITY)
    reason: str = Field(min_length=1, max_length=MAX_REASON_CHARACTERS)
    correlation_id: str = Field(alias="correlationId", min_length=1, max_length=MAX_IDENTIFIER_LENGTH)
    campaign: Optional[str] = Field(default=None, max_length=256)
    redemption_deadline: Optional[str] = Field(default=None, alias="redemptionDeadline", max_length=64)
    surface_profile_id: Optional[str] = Field(
        default=None,
        alias="surfaceProfileId",
        min_length=1,
        max_length=MAX_IDENTIFIER_LENGTH,
    )
    trial_duration_hours: Optional[int] = Field(
        default=None,
        alias="trialDurationHours",
        ge=TRIAL_MINIMUM_AUTHORITY_DURATION_HOURS,
        le=TRIAL_MAXIMUM_TOTAL_DURATION_HOURS,
    )


class ProductSurfaceProfileCreateRequest(ApiModel):
    name: str = Field(min_length=1, max_length=256)
    description: str = Field(min_length=1, max_length=512)
    analysis_ids: list[str] = Field(
        alias="analysisIds",
        max_length=len(product_surface_ids(PRODUCT_SURFACE_KIND_ANALYSIS)),
    )
    main_tab_ids: list[str] = Field(
        alias="mainTabIds",
        min_length=1,
        max_length=len(product_surface_ids(PRODUCT_SURFACE_KIND_MAIN_TAB)),
    )
    reason: str = Field(min_length=1, max_length=MAX_REASON_CHARACTERS)
    correlation_id: str = Field(
        alias="correlationId",
        min_length=1,
        max_length=MAX_IDENTIFIER_LENGTH,
    )


class ProductSurfaceProfileArchiveRequest(ApiModel):
    reason: str = Field(min_length=1, max_length=MAX_REASON_CHARACTERS)
    correlation_id: str = Field(
        alias="correlationId",
        min_length=1,
        max_length=MAX_IDENTIFIER_LENGTH,
    )


class LicenseSurfaceProfileAssignmentRequest(ApiModel):
    surface_profile_id: Optional[str] = Field(
        default=None,
        alias="surfaceProfileId",
        min_length=1,
        max_length=MAX_IDENTIFIER_LENGTH,
    )
    reason: str = Field(min_length=1, max_length=MAX_REASON_CHARACTERS)
    correlation_id: str = Field(
        alias="correlationId",
        min_length=1,
        max_length=MAX_IDENTIFIER_LENGTH,
    )


class AdminSerialBatchRevocationPreviewRequest(ApiModel):
    reason_code: str = Field(alias="reasonCode")
    note: Optional[str] = Field(default=None, min_length=1, max_length=MAX_REASON_CHARACTERS)
    correlation_id: str = Field(
        alias="correlationId",
        min_length=1,
        max_length=MAX_IDENTIFIER_LENGTH,
    )

    @field_validator("reason_code")
    @classmethod
    def validate_reason_code(cls, value: str) -> str:
        normalized = value.strip()
        if normalized not in SERIAL_BATCH_REVOCATION_REASONS:
            raise ValueError("reasonCode is not valid for serial batch revocation")
        return normalized


class AdminSerialBatchRevocationExecuteRequest(
    AdminSerialBatchRevocationPreviewRequest
):
    expected_state_digest: str = Field(
        alias="expectedStateDigest",
        pattern="^[0-9a-f]{64}$",
    )
    idempotency_key: str = Field(
        alias="idempotencyKey",
        min_length=1,
        max_length=MAX_IDEMPOTENCY_KEY_CHARACTERS,
    )


class AdminCatalogStageRequest(ApiModel):
    document: dict[str, Any]
    reason_code: str = Field(alias="reasonCode")
    note: Optional[str] = Field(
        default=None,
        min_length=1,
        max_length=MAX_REASON_CHARACTERS,
    )
    idempotency_key: str = Field(
        alias="idempotencyKey",
        min_length=1,
        max_length=MAX_IDEMPOTENCY_KEY_CHARACTERS,
    )
    correlation_id: str = Field(
        alias="correlationId",
        min_length=1,
        max_length=MAX_IDENTIFIER_LENGTH,
    )

    @field_validator("reason_code")
    @classmethod
    def validate_reason_code(cls, value: str) -> str:
        normalized = value.strip()
        if normalized not in CATALOG_RELEASE_REASONS:
            raise ValueError("reasonCode is not valid for catalog administration")
        return normalized


class AdminCatalogPublicationPreviewRequest(ApiModel):
    reason_code: str = Field(alias="reasonCode")
    note: Optional[str] = Field(
        default=None,
        min_length=1,
        max_length=MAX_REASON_CHARACTERS,
    )
    correlation_id: str = Field(
        alias="correlationId",
        min_length=1,
        max_length=MAX_IDENTIFIER_LENGTH,
    )

    @field_validator("reason_code")
    @classmethod
    def validate_reason_code(cls, value: str) -> str:
        normalized = value.strip()
        if normalized not in CATALOG_RELEASE_REASONS:
            raise ValueError("reasonCode is not valid for catalog administration")
        return normalized


class AdminCatalogPublicationExecuteRequest(
    AdminCatalogPublicationPreviewRequest
):
    expected_state_digest: str = Field(
        alias="expectedStateDigest",
        pattern="^[0-9a-f]{64}$",
    )
    idempotency_key: str = Field(
        alias="idempotencyKey",
        min_length=1,
        max_length=MAX_IDEMPOTENCY_KEY_CHARACTERS,
    )


class AdminTrialExtensionPreviewRequest(ApiModel):
    reason_code: str = Field(alias="reasonCode")
    note: Optional[str] = Field(default=None, min_length=1, max_length=MAX_REASON_CHARACTERS)
    correlation_id: str = Field(
        alias="correlationId",
        min_length=1,
        max_length=MAX_IDENTIFIER_LENGTH,
    )

    @field_validator("reason_code")
    @classmethod
    def validate_reason_code(cls, value: str) -> str:
        normalized = value.strip()
        if normalized not in SUPPORT_TRIAL_EXTENSION_REASONS:
            raise ValueError("reasonCode is not valid for a trial extension")
        return normalized


class CheckoutSessionRequest(ApiModel):
    sku_id: str = Field(alias="skuId", min_length=1, max_length=MAX_IDENTIFIER_LENGTH)
    idempotency_key: str = Field(
        alias="idempotencyKey",
        min_length=1,
        max_length=MAX_IDEMPOTENCY_KEY_CHARACTERS,
    )
    correlation_id: str = Field(
        alias="correlationId",
        min_length=1,
        max_length=MAX_IDENTIFIER_LENGTH,
    )


class BillingPortalSessionRequest(ApiModel):
    idempotency_key: str = Field(
        alias="idempotencyKey",
        min_length=1,
        max_length=MAX_IDEMPOTENCY_KEY_CHARACTERS,
    )
    correlation_id: str = Field(
        alias="correlationId",
        min_length=1,
        max_length=MAX_IDENTIFIER_LENGTH,
    )


class PrivacyExportRequest(ApiModel):
    correlation_id: str = Field(
        alias="correlationId",
        min_length=1,
        max_length=MAX_IDENTIFIER_LENGTH,
    )


class PrivacyDeletionRequest(ApiModel):
    acknowledge_permanent_account_closure: bool = Field(
        alias="acknowledgePermanentAccountClosure"
    )
    idempotency_key: str = Field(
        alias="idempotencyKey",
        min_length=1,
        max_length=MAX_IDEMPOTENCY_KEY_CHARACTERS,
    )
    correlation_id: str = Field(
        alias="correlationId",
        min_length=1,
        max_length=MAX_IDENTIFIER_LENGTH,
    )

    @field_validator("acknowledge_permanent_account_closure")
    @classmethod
    def validate_acknowledgement(cls, value: bool) -> bool:
        if value is not True:
            raise ValueError("permanent account closure must be acknowledged")
        return value


class PrivacyDeletionCancelRequest(ApiModel):
    correlation_id: str = Field(
        alias="correlationId",
        min_length=1,
        max_length=MAX_IDENTIFIER_LENGTH,
    )


class AdminPrivacyDeletionPreviewRequest(ApiModel):
    reason_code: str = Field(alias="reasonCode")
    note: Optional[str] = Field(
        default=None,
        min_length=1,
        max_length=MAX_REASON_CHARACTERS,
    )
    correlation_id: str = Field(
        alias="correlationId",
        min_length=1,
        max_length=MAX_IDENTIFIER_LENGTH,
    )

    @field_validator("reason_code")
    @classmethod
    def validate_reason_code(cls, value: str) -> str:
        normalized = value.strip()
        if normalized not in PRIVACY_DELETION_REASONS:
            raise ValueError("reasonCode is not valid for account deletion")
        return normalized


class AdminPrivacyDeletionExecuteRequest(AdminPrivacyDeletionPreviewRequest):
    expected_state_digest: str = Field(
        alias="expectedStateDigest",
        pattern="^[0-9a-f]{64}$",
    )
    idempotency_key: str = Field(
        alias="idempotencyKey",
        min_length=1,
        max_length=MAX_IDEMPOTENCY_KEY_CHARACTERS,
    )


class AdminTrialExtensionExecuteRequest(AdminTrialExtensionPreviewRequest):
    idempotency_key: str = Field(
        alias="idempotencyKey",
        min_length=1,
        max_length=MAX_IDEMPOTENCY_KEY_CHARACTERS,
    )
    expected_revocation_generation: int = Field(
        alias="expectedRevocationGeneration",
        ge=0,
    )


class AdminDeviceRecoveryPreviewRequest(ApiModel):
    reason_code: str = Field(alias="reasonCode")
    note: Optional[str] = Field(default=None, min_length=1, max_length=MAX_REASON_CHARACTERS)
    correlation_id: str = Field(
        alias="correlationId",
        min_length=1,
        max_length=MAX_IDENTIFIER_LENGTH,
    )

    @field_validator("reason_code")
    @classmethod
    def validate_reason_code(cls, value: str) -> str:
        normalized = value.strip()
        if normalized not in SUPPORT_DEVICE_RECOVERY_REASONS:
            raise ValueError("reasonCode is not valid for device recovery")
        return normalized


class AdminDeviceRecoveryExecuteRequest(AdminDeviceRecoveryPreviewRequest):
    expected_state_digest: str = Field(
        alias="expectedStateDigest",
        pattern="^[0-9a-f]{64}$",
    )
    idempotency_key: str = Field(
        alias="idempotencyKey",
        min_length=1,
        max_length=MAX_IDEMPOTENCY_KEY_CHARACTERS,
    )


class AdminGrantRevocationPreviewRequest(ApiModel):
    reason_code: str = Field(alias="reasonCode")
    note: Optional[str] = Field(default=None, min_length=1, max_length=MAX_REASON_CHARACTERS)
    correlation_id: str = Field(
        alias="correlationId",
        min_length=1,
        max_length=MAX_IDENTIFIER_LENGTH,
    )

    @field_validator("reason_code")
    @classmethod
    def validate_reason_code(cls, value: str) -> str:
        normalized = value.strip()
        if normalized not in SUPPORT_GRANT_REVOCATION_REASONS:
            raise ValueError("reasonCode is not valid for a grant revocation")
        return normalized


class AdminGrantRevocationExecuteRequest(AdminGrantRevocationPreviewRequest):
    idempotency_key: str = Field(
        alias="idempotencyKey",
        min_length=1,
        max_length=MAX_IDEMPOTENCY_KEY_CHARACTERS,
    )
    expected_revocation_generation: int = Field(
        alias="expectedRevocationGeneration",
        ge=0,
    )


class AdminAuditSearchRequest(ApiModel):
    action: Optional[str] = Field(default=None, min_length=1, max_length=MAX_IDENTIFIER_LENGTH)
    target_type: Optional[str] = Field(
        default=None,
        alias="targetType",
        min_length=1,
        max_length=MAX_IDENTIFIER_LENGTH,
    )
    target_id: Optional[str] = Field(
        default=None,
        alias="targetId",
        min_length=1,
        max_length=MAX_IDENTIFIER_LENGTH,
    )
    actor_id: Optional[str] = Field(
        default=None,
        alias="actorId",
        min_length=1,
        max_length=MAX_IDENTIFIER_LENGTH,
    )
    occurred_from: Optional[str] = Field(
        default=None,
        alias="occurredFrom",
        min_length=1,
        max_length=64,
    )
    occurred_to: Optional[str] = Field(
        default=None,
        alias="occurredTo",
        min_length=1,
        max_length=64,
    )
    cursor: Optional[str] = Field(default=None, min_length=1, max_length=2048)
    limit: int = Field(default=100, ge=1, le=500)


class AdminAuditExportRequest(AdminAuditSearchRequest):
    cursor: None = None
    limit: int = Field(default=5000, ge=1, le=5000)
    correlation_id: str = Field(
        alias="correlationId",
        min_length=1,
        max_length=MAX_IDENTIFIER_LENGTH,
    )


class OfflineTrialSubmissionRequest(ApiModel):
    offline_request: dict[str, Any] = Field(alias="offlineRequest")


class OfflineTrialStatusRequest(ApiModel):
    request_id: str = Field(
        alias="requestId",
        min_length=1,
        max_length=MAX_IDENTIFIER_LENGTH,
    )
    nonce: str = Field(min_length=1, max_length=256)


class AdminOfflineApprovalRequest(ApiModel):
    reason_code: str = Field(alias="reasonCode")
    note: Optional[str] = Field(default=None, min_length=1, max_length=MAX_REASON_CHARACTERS)
    expected_decision_generation: int = Field(
        alias="expectedDecisionGeneration",
        ge=0,
    )
    idempotency_key: str = Field(
        alias="idempotencyKey",
        min_length=1,
        max_length=MAX_IDEMPOTENCY_KEY_CHARACTERS,
    )
    correlation_id: str = Field(
        alias="correlationId",
        min_length=1,
        max_length=MAX_IDENTIFIER_LENGTH,
    )

    @field_validator("reason_code")
    @classmethod
    def validate_reason_code(cls, value: str) -> str:
        normalized = value.strip()
        if normalized not in OFFLINE_TRIAL_APPROVAL_REASONS:
            raise ValueError("reasonCode is not valid for offline trial approval")
        return normalized


class AdminOfflineRejectionRequest(AdminOfflineApprovalRequest):
    @field_validator("reason_code")
    @classmethod
    def validate_reason_code(cls, value: str) -> str:
        normalized = value.strip()
        if normalized not in OFFLINE_TRIAL_REJECTION_REASONS:
            raise ValueError("reasonCode is not valid for offline trial rejection")
        return normalized


class OfflineActivationRequest(ApiModel):
    offline_request: dict[str, Any] = Field(alias="offlineRequest")
    serial: str = Field(min_length=1, max_length=128)
    idempotency_key: str = Field(
        alias="idempotencyKey",
        min_length=1,
        max_length=MAX_IDEMPOTENCY_KEY_CHARACTERS,
    )
    correlation_id: str = Field(
        alias="correlationId",
        min_length=1,
        max_length=MAX_IDENTIFIER_LENGTH,
    )
    target_license_id: Optional[str] = Field(
        default=None,
        alias="targetLicenseId",
        max_length=MAX_IDENTIFIER_LENGTH,
    )


class OfflineRenewalRequest(ApiModel):
    offline_request: dict[str, Any] = Field(alias="offlineRequest")
    idempotency_key: str = Field(
        alias="idempotencyKey",
        min_length=1,
        max_length=MAX_IDEMPOTENCY_KEY_CHARACTERS,
    )
    correlation_id: str = Field(
        alias="correlationId",
        min_length=1,
        max_length=MAX_IDENTIFIER_LENGTH,
    )


class OfflineReleaseRequest(ApiModel):
    offline_request: dict[str, Any] = Field(alias="offlineRequest")


class LicenseDocumentResponse(ApiModel):
    license_id: str = Field(alias="licenseId")
    activation_id: str = Field(alias="activationId")
    device_id: str = Field(alias="deviceId")
    recovered_existing_device: bool = Field(alias="recoveredExistingDevice")
    idempotent_replay: bool = Field(alias="idempotentReplay")
    license_document: dict[str, Any] = Field(alias="licenseDocument")


class ApiErrorResponse(ApiModel):
    code: str
    message: str
    retryable: bool = False
    existing_resource_id: Optional[str] = Field(default=None, alias="existingResourceId")


__all__ = [
    "AdminCatalogPublicationExecuteRequest",
    "AdminCatalogPublicationPreviewRequest",
    "AdminCatalogStageRequest",
    "AdminDeviceRecoveryExecuteRequest",
    "AdminDeviceRecoveryPreviewRequest",
    "AdminOfflineApprovalRequest",
    "AdminOfflineRejectionRequest",
    "AdminAuditExportRequest",
    "AdminAuditSearchRequest",
    "AdminGrantRevocationExecuteRequest",
    "AdminGrantRevocationPreviewRequest",
    "AdminSerialBatchRevocationExecuteRequest",
    "AdminSerialBatchRevocationPreviewRequest",
    "AdminTrialExtensionExecuteRequest",
    "AdminTrialExtensionPreviewRequest",
    "AccountLicenseActivationRequest",
    "ActivationDeactivateRequest",
    "ActivationRefreshRequest",
    "ApiErrorResponse",
    "BillingPortalSessionRequest",
    "CheckoutSessionRequest",
    "DeviceRenameRequest",
    "DeviceEnrollmentRequest",
    "DeviceProofRequest",
    "LicenseDocumentResponse",
    "LicenseSurfaceProfileAssignmentRequest",
    "OfflineActivationRequest",
    "OfflineTrialStatusRequest",
    "OfflineTrialSubmissionRequest",
    "OfflineRenewalRequest",
    "OfflineReleaseRequest",
    "OrganizationCreateRequest",
    "OrganizationInvitationAcceptRequest",
    "OrganizationInvitationCreateRequest",
    "OrganizationLicenseAttachRequest",
    "OrganizationSeatAssignRequest",
    "OrganizationSeatReleaseRequest",
    "ProductSurfaceProfileArchiveRequest",
    "ProductSurfaceProfileCreateRequest",
    "RemoteDeviceReleaseRequest",
    "SerialBatchRequest",
    "SerialClaimRequest",
    "SerialRedemptionRequest",
    "TrialStartRequest",
]

