# coding: utf-8

"""
    File Service API v2 - Token and Tenant Management

    File Service API v2 - Token and tenant management endpoints

    The version of the OpenAPI document: 0.1.0
    Generated by OpenAPI Generator (https://openapi-generator.tech)

    Do not edit the class manually.
"""  # noqa: E501

import warnings
from pydantic import validate_call, Field, StrictFloat, StrictStr, StrictInt
from typing import Any, Dict, List, Optional, Tuple, Union, TypeAlias

from pydantic import Field, StrictBytes, StrictStr
from typing import Any, Optional, Tuple, Union
from typing_extensions import Annotated
from istari_digital_client.storage.models.create_multipart_upload_request import CreateMultipartUploadRequest
from istari_digital_client.storage.models.create_multipart_upload_response import CreateMultipartUploadResponse
from istari_digital_client.storage.models.finalize_multipart_upload_request import FinalizeMultipartUploadRequest
from istari_digital_client.storage.models.page_tenant import PageTenant
from istari_digital_client.storage.models.temporary_url import TemporaryURL
from istari_digital_client.storage.models.tenant_public_key import TenantPublicKey
from istari_digital_client.api_client import ApiClient, RequestSerialized
from istari_digital_client.configuration import Configuration
from istari_digital_client.log_utils import log_method
from istari_digital_client.exceptions import ConflictException

from istari_digital_client.storage.models.completed_part_type_def import CompletedPartTypeDef
from istari_digital_client.storage.models.finalize_multipart_upload_action import FinalizeMultipartUploadAction

from istari_digital_client.v2.models.token import Token
from istari_digital_client.v2.models.token_with_properties import TokenWithProperties
from istari_digital_client.v2.models.properties import Properties
from istari_digital_client.v2.models.source import Source
from istari_digital_client.v2.models.file import File
from istari_digital_client.v2.models.file_revision import FileRevision
from istari_digital_client.v2.models.file_revision_archive_status import FileRevisionArchiveStatus
from istari_digital_client.v2.models.archive_status_name import ArchiveStatusName
from istari_digital_client.v2.models.new_source import NewSource


import uuid
from datetime import datetime, timezone
import os
from pathlib import Path

import istari_digital_core

PathLike = Union[str, os.PathLike, Path]
JSON: TypeAlias = dict[str, "JSON"] | list["JSON"] | str | int | float | bool | None


class StorageApi:
    """NOTE: This class is auto generated by OpenAPI Generator
    Ref: https://openapi-generator.tech

    Do not edit the class manually.
    """

    def __init__(self, config: Configuration, api_client=None) -> None:
        if api_client is None:
            api_client = ApiClient.get_default()
        self.api_client = api_client
        self.config = config

    @staticmethod
    def _is_azure_url(url: str) -> bool:
        """
        Check if URL is for Azure Blob Storage based on Rust implementation.

        :param url: URL to check
        :type url: str
        :return: True if URL is for Azure Blob Storage, False otherwise
        :rtype: bool
        """
        return (
            'blob.core.windows.net' in url or
            ('se=' in url and 'sp=' in url and 'sv=' in url)
        )

    @validate_call
    @log_method
    def _upload(
        self,
        temp_url: TemporaryURL,
        file_bytes: bytes,
        http_request_timeout_secs: Annotated[StrictInt | None, Field(ge=0)] = None,
        _content_type: Optional[StrictStr] = None,
        _headers: Optional[Dict[StrictStr, Any]] = None,
    ) -> None:
        """
        Upload bytes to a temporary URL.

        :param temp_url: The temporary URL object containing the upload URL (required)
        :type temp_url: TemporaryURL
        :param file_bytes: The bytes to upload (required)
        :type file_bytes: bytes
        :param http_request_timeout_secs: timeout setting for this request
        :type http_request_timeout_secs: int, optional
        :param _content_type: override content type for the request
        :type _content_type: str, optional
        :param _headers: override headers for the request
        :type _headers: dict, optional
        """

        _param = self.__upload_serialize(
            temp_url=temp_url,
            file_bytes=file_bytes,
            _content_type=_content_type,
            _headers=_headers,
        )

        request_timeout = (
            http_request_timeout_secs if http_request_timeout_secs is not None
            else self.config.http_request_timeout_secs
        )

        response_data = self.api_client.call_api(
            *_param,
            _request_timeout=request_timeout,
        )
        response_data.read()

        # For upload, we don't expect a response body, so we don't need to deserialize
        # The response_deserialize method will handle error checking automatically
        _response_types_map: Dict[str, Optional[str]] = {
            '200': None,  # Success with no response body
            '201': None,  # Created with no response body
        }

        self.api_client.response_deserialize(
            response_data=response_data,
            response_types_map=_response_types_map,
        )

    def __upload_serialize(
        self,
        temp_url: TemporaryURL,
        file_bytes: bytes,
        _content_type: str | None,
        _headers: Dict[str, Any] | None,
    ) -> RequestSerialized:
        """
        Serialize the upload request parameters.

        :param temp_url: Temporary URL object containing the upload URL
        :type temp_url: TemporaryURL
        :param file_bytes: Bytes data to upload
        :type file_bytes: bytes
        :param _content_type: Optional content type override
        :type _content_type: str | None
        :param _headers: Optional headers override
        :type _headers: Dict[str, Any] | None
        :return: Serialized request parameters
        :rtype: RequestSerialized
        """

        _collection_formats: Dict[str, str] = {}
        _path_params: Dict[str, str] = {}
        _query_params: List[Tuple[str, str]] = []
        _header_params: Dict[str, Optional[str]] = _headers or {}
        _form_params: List[Tuple[str, str]] = []
        _files: Dict[
            str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]]
        ] = {}
        _body_params: Optional[bytes] = None

        # Set the request body to the file bytes
        if file_bytes is not None:
            _body_params = file_bytes

        # Set required headers based on the Rust implementation
        if _content_type:
            _header_params['Content-Type'] = _content_type
        else:
            _header_params['Content-Type'] = 'application/octet-stream'

        # Always set Content-Length header (required for uploads)
        _header_params['Content-Length'] = str(len(file_bytes)) if file_bytes else '0'

        # Check if this is an Azure URL and add required header
        url = temp_url.url
        if self._is_azure_url(url):
            _header_params['x-ms-blob-type'] = 'BlockBlob'

        # No authentication needed for temporary URLs (they're pre-signed)
        _auth_settings: List[str] = []

        return self.api_client.param_serialize(
            method='PUT',  # Based on Rust implementation
            resource_path=temp_url.url,  # Use the full URL from temp_url
            path_params=_path_params,
            query_params=_query_params,
            header_params=_header_params,
            body=_body_params,
            post_params=_form_params,
            files=_files,
            auth_settings=_auth_settings,
            collection_formats=_collection_formats,
            _host='',  # Empty host since we're using the full URL
            _request_auth=None
        )

    def _upload_properties(self, properties_token: Token, properties_bytes: bytes) -> None:
        """
        Upload properties with 409 conflict handling.

        :param properties_token: Token containing SHA and salt for the properties
        :type properties_token: Token
        :param properties_bytes: Serialized properties data to upload
        :type properties_bytes: bytes
        :raises ConflictException: If SHA already exists (caught and ignored)
        """
        try:
            properties_upload_url = self.generate_upload_url(properties_token.sha)
            self._upload(
                temp_url=properties_upload_url,
                file_bytes=properties_bytes,
            )
        except ConflictException:
            # SHA already exists, skip upload
            pass

    def _upload_content_single(self, file_bytes: bytes, salt: str | None = None) -> Token:
        """
        Upload content using single upload for bytes data.

        :param file_bytes: The bytes to upload
        :type file_bytes: bytes
        :param salt: Optional salt value for creating the token
        :type salt: str | None
        :return: Token containing SHA and salt for the uploaded content
        :rtype: Token
        :raises ConflictException: If SHA already exists (caught and ignored)
        """

        # Create content token with optional salt
        if salt is not None:
            content_token = Token.from_bytes(file_bytes, salt)
        else:
            content_token = Token.from_bytes(file_bytes)

        # Get upload URL and upload content with 409 handling
        try:
            content_upload_url = self.generate_upload_url(content_token.sha)
            self._upload(
                temp_url=content_upload_url,
                file_bytes=file_bytes,
            )
        except ConflictException:
            # SHA already exists, skip upload
            pass

        return content_token

    def _upload_content_multipart(self, file_bytes: bytes, salt: str | None = None) -> Token:
        """
        Upload content using multipart upload for large bytes data.

        :param file_bytes: The bytes to upload
        :type file_bytes: bytes
        :param salt: Optional salt value for creating the token
        :type salt: str | None
        :return: Token containing SHA and salt for the uploaded content
        :rtype: Token
        :raises ValueError: If upload fails or parts don't match expected count
        :raises Exception: If multipart upload creation fails
        """

        # Create content token with optional salt
        if salt is not None:
            content_token = Token.from_bytes(file_bytes, salt)
        else:
            content_token = Token.from_bytes(file_bytes)

        chunk_size = self.config.multipart_chunksize or 128 * 1024 * 1024  # type: ignore[operator]
        bytes_len = len(file_bytes)
        # AWS S3 supports up to 10,000 parts.
        chunk_count = (bytes_len // chunk_size) + 1
        if bytes_len % chunk_size == 0:
            # We're right on the boundary.
            chunk_count -= 1

        create_request = CreateMultipartUploadRequest(
            sha=content_token.sha,
            num_parts=chunk_count
        )

        try:
            upload_metadata = self.create_multipart_upload(create_request)
        except Exception as e:
            if "Sha already exists" in str(e):
                return content_token
            else:
                raise

        completed_parts = []
        pos = 0

        for part_num in range(1, len(upload_metadata.upload_urls) + 1):
            upload_url = upload_metadata.upload_urls.get(str(part_num))
            if upload_url is None:
                self._cancel_multipart_upload(content_token.sha, upload_metadata.upload_id)
                raise ValueError("invalid state detected, missing upload URL for part")

            end_pos = pos + chunk_size
            if end_pos > bytes_len:
                # This is the last part.
                if part_num != len(upload_metadata.upload_urls):
                    # Should never happen.
                    self._cancel_multipart_upload(content_token.sha, upload_metadata.upload_id)
                    raise ValueError("invalid state detected, mismatched number of parts and chunks")
                end_pos = bytes_len

            chunk_data = file_bytes[pos:end_pos]

            try:
                response_data = self._upload_multipart_chunk(upload_url, chunk_data)
            except Exception as e:
                self._cancel_multipart_upload(content_token.sha, upload_metadata.upload_id)
                raise

            pos = end_pos

            # Check response status and handle ETag
            is_azure = self._is_azure_url(upload_url.url)

            if response_data.status == 200 and not is_azure:
                # We successfully uploaded the part.
                # We need to convert the header value to a string.
                # The ETag header is always quoted, so we need to strip the quotes.
                etag_header = response_data.getheader("ETag")
                if etag_header:
                    try:
                        e_tag = etag_header.strip('"')
                        completed_parts.append(CompletedPartTypeDef(
                            ETag=e_tag,
                            PartNumber=part_num
                        ))
                    except Exception:
                        self._cancel_multipart_upload(content_token.sha, upload_metadata.upload_id)
                        raise ValueError("invalid state detected, ETag header is not valid UTF-8")
                else:
                    self._cancel_multipart_upload(content_token.sha, upload_metadata.upload_id)
                    raise ValueError("invalid state detected, ETag header missing")
            elif response_data.status == 201 and is_azure:
                # We successfully staged the block to Azure Storage Blob Service.
                # With Azure Storage Blob Service we only get back a x-ms-request-id header which we don't
                # care about here. All we need to do is register the completed part as when we go to complete
                # the multipart upload we get the list of staged blocks for the SHA/blob from the client and
                # ensure that the number of those matches the number of completed parts we pass to it.
                completed_parts.append(CompletedPartTypeDef(
                    ETag="N/A",
                    PartNumber=part_num
                ))
            else:
                # Something went wrong, cancel the upload and return an error.
                self._cancel_multipart_upload(content_token.sha, upload_metadata.upload_id)
                raise ValueError(f"Upload failed with status {response_data.status}")

        if len(completed_parts) != chunk_count:
            # Should never happen.
            self._cancel_multipart_upload(content_token.sha, upload_metadata.upload_id)
            raise ValueError(
                "invalid state detected, the number of completed parts does not equal the number of chunks")

        try:
            finalize_request = FinalizeMultipartUploadRequest(
                action=FinalizeMultipartUploadAction.COMPLETE,
                sha=content_token.sha,
                upload_id=upload_metadata.upload_id,
                completed_parts=completed_parts
            )
            self.finalize_multipart_upload(finalize_request)
            return content_token
        except Exception as e:
            self._cancel_multipart_upload(content_token.sha, upload_metadata.upload_id)
            raise

    def _cancel_multipart_upload(self, sha: str, upload_id: str) -> None:
        """
        Cancel a multipart upload.

        :param sha: SHA hash of the content being uploaded
        :type sha: str
        :param upload_id: Unique identifier for the multipart upload to cancel
        :type upload_id: str
        """
        cancel_request = FinalizeMultipartUploadRequest(
            action=FinalizeMultipartUploadAction.CANCEL,
            sha=sha,
            upload_id=upload_id,
            completed_parts=None
        )
        self.finalize_multipart_upload(cancel_request)

    def _upload_multipart_chunk(self, upload_url: TemporaryURL, chunk_data: bytes) -> Any:
        """
        Upload a single chunk using the existing _upload method but return response data.

        :param upload_url: Temporary URL for uploading the chunk
        :type upload_url: TemporaryURL
        :param chunk_data: Bytes data for this chunk
        :type chunk_data: bytes
        :return: Raw response data from the upload request
        :rtype: Any
        """
        _param = self.__upload_serialize(
            temp_url=upload_url,
            file_bytes=chunk_data,
            _content_type=None,
            _headers=None,
        )

        response_data = self.api_client.call_api(
            *_param,
            _request_timeout=self.config.http_request_timeout_secs,
        )
        response_data.read()

        # Return the raw response data so we can check status and headers
        return response_data

    @validate_call
    @log_method
    def _download(
        self,
        temp_url: TemporaryURL,
        http_request_timeout_secs: Annotated[StrictInt | None, Field(ge=0)] = None,
        _headers: Optional[Dict[StrictStr, Any]] = None,
    ) -> bytes:
        """
        Download bytes from a temporary URL.

        :param temp_url: The temporary URL object containing the download URL (required)
        :type temp_url: TemporaryURL
        :param http_request_timeout_secs: timeout setting for this request
        :type http_request_timeout_secs: int, optional
        :param _headers: override headers for the request
        :type _headers: dict, optional
        :return: Downloaded bytes data
        :rtype: bytes
        """

        _param = self.__download_serialize(
            temp_url=temp_url,
            _headers=_headers,
        )

        request_timeout = (
            http_request_timeout_secs if http_request_timeout_secs is not None
            else self.config.http_request_timeout_secs
        )

        response_data = self.api_client.call_api(
            *_param,
            _request_timeout=request_timeout,
        )
        response_data.read()

        # For download, we expect binary content in the response body
        _response_types_map: Dict[str, Optional[str]] = {
            '200': "bytearray",  # Success with binary response body
        }

        response = self.api_client.response_deserialize(
            response_data=response_data,
            response_types_map=_response_types_map,
        )

        return bytes(response.data)

    def __download_serialize(
        self,
        temp_url: TemporaryURL,
        _headers: Dict[str, Any] | None,
    ) -> RequestSerialized:
        """
        Serialize the download request parameters.

        :param temp_url: Temporary URL object containing the download URL
        :type temp_url: TemporaryURL
        :param _headers: Optional headers override
        :type _headers: Dict[str, Any] | None
        :return: Serialized request parameters
        :rtype: RequestSerialized
        """

        _collection_formats: Dict[str, str] = {}
        _path_params: Dict[str, str] = {}
        _query_params: List[Tuple[str, str]] = []
        _header_params: Dict[str, Optional[str]] = _headers or {}
        _form_params: List[Tuple[str, str]] = []
        _files: Dict[
            str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]]
        ] = {}
        _body_params: Optional[bytes] = None

        # No body for download requests
        _body_params = None

        # No authentication needed for temporary URLs (they're pre-signed)
        _auth_settings: List[str] = []

        return self.api_client.param_serialize(
            method='GET',  # Based on Rust implementation
            resource_path=temp_url.url,  # Use the full URL from temp_url
            path_params=_path_params,
            query_params=_query_params,
            header_params=_header_params,
            body=_body_params,
            post_params=_form_params,
            files=_files,
            auth_settings=_auth_settings,
            collection_formats=_collection_formats,
            _host='',  # Empty host since we're using the full URL
            _request_auth=None
        )

    @log_method
    def _download_token_data(self, token: Token) -> bytes:
        """
        Download data by SHA and salt, with token validation.

        This method can be used to download any type of data (content or properties)
        that is stored and accessed via a token.

        :param token: Access token for retrieving the data
        :type token: Token
        :return: Downloaded and validated data
        :rtype: bytes
        :raises ValueError: If hash validation fails
        """
        # Get download URL using existing method
        download_url = self.generate_download_url(token.sha)

        # Download data
        data = self._download(temp_url=download_url)

        # Validate data matches expected hash (like Rust implementation)
        if not istari_digital_core.compare_hash(token.sha, token.salt, data):
            raise ValueError("Hash of downloaded data does not match expected hash")

        return data

    @log_method
    def read_contents(self, token: Token) -> bytes:
        """
        Read and return the raw contents of a file associated with a token.

        This method retrieves the file data as a byte stream using the specified token,
        which grants access to the protected resource.

        :param token: Access token for retrieving the file contents.
        :type token: Token
        :return: Raw file content as bytes
        :rtype: bytes
        """

        return self._download_token_data(token)

    @log_method
    def read_properties(self, token: Token) -> Properties:
        """
        Retrieve metadata properties for a file associated with a token.

        This method returns descriptive metadata about the file identified by the provided access token.
        The returned `Properties` object includes non-content attributes such as file name, extension,
        size, MIME type, and optional descriptive metadata.

        Fields include:
        - `name`: Full file name with extension (e.g., "foo.JPG").
        - `stem`: File name without extension (e.g., "foo").
        - `extension`: Lowercased file extension without the dot (e.g., "jpg").
        - `suffix`: File suffix with original casing and a leading dot (e.g., ".JPG").
        - `size`: File size in bytes.
        - `mime_type`: MIME type of the file (e.g., "image/jpeg").
        - `description`: Optional description set on the file.
        - `version_name`: Optional version label.
        - `external_identifier`: Optional external reference ID.
        - `display_name`: Optional user-facing display name.

        :param token: Access token used to retrieve file properties.
        :type token: Token
        :return: Properties object containing file metadata
        :rtype: Properties
        """

        data = self._download_token_data(token)

        return Properties.from_bytes(data)

    @log_method
    def create_revision(
        self,
        file_path: PathLike,
        sources: list[NewSource | str] | None = None,
        display_name: str | None = None,
        description: str | None = None,
        version_name: str | None = None,
        external_identifier: str | None = None,
    ) -> FileRevision:
        """
        Create a new file revision from a given file path and optional metadata.

        This method uploads a file as a new revision and optionally attaches metadata such as
        sources, display name, description, version name, and an external identifier.

        :param file_path: Filesystem path to the file being uploaded as a revision.
        :type file_path: PathLike
        :param sources: Optional list of sources associated with the revision. Each item may be a
                        NewSource instance or a string source name.
        :type sources: list[NewSource | str] | None
        :param display_name: Optional human-readable name for the revision.
        :type display_name: str | None
        :param description: Optional text describing the revision's contents or purpose.
        :type description: str | None
        :param version_name: Optional user-defined version label for the revision.
        :type version_name: str | None
        :param external_identifier: Optional external system identifier for the revision.
        :type external_identifier: str | None
        :return: Created file revision object
        :rtype: FileRevision
        """

        properties = Properties.from_path(
            path=Path(file_path),
            description=description,
            version_name=version_name,
            external_identifier=external_identifier,
            display_name=display_name,
        )

        properties_bytes = properties.to_bytes()

        properties_token = Token.from_bytes(properties_bytes)

        # Upload properties
        self._upload_properties(properties_token, properties_bytes)

        # Read file content
        with open(file_path, 'rb') as f:
            file_bytes = f.read()

        # Upload content - conditional based on file size
        if properties.size <= self.config.multipart_threshold:  # type: ignore[operator]
            # Small file - use regular upload
            content_token = self._upload_content_single(file_bytes)
        else:
            # Large file - use multipart upload
            content_token = self._upload_content_multipart(file_bytes)

        # Create sources list
        source_list = [
            Source(
                revision_id=source if isinstance(source, str) else source.revision_id,
                file_id=None,
                resource_type=None,
                resource_id=None,
                relationship_identifier=None if isinstance(source, str) else source.relationship_identifier,
            )
            for source in (sources or [])
        ]

        # Create FileRevision following the same pattern as from_storage_revision
        file_revision_id = str(uuid.uuid4())

        file_revision_archive_status = FileRevisionArchiveStatus(
            id=str(uuid.uuid4()),
            created=datetime.now(timezone.utc),
            name=ArchiveStatusName.ACTIVE,
            reason="Initial",
            created_by_id=None,
            file_revision_id=file_revision_id,
        )

        return FileRevision(
            id=file_revision_id,
            created=datetime.now(timezone.utc),
            file_id=None,
            content_token=content_token,
            properties_token=properties_token,
            archive_status_history=[file_revision_archive_status],
            name=properties.file_name,
            extension=properties.extension,
            size=properties.size,
            description=properties.description,
            mime=properties.mime,
            version_name=properties.version_name,
            external_identifier=properties.external_identifier,
            display_name=properties.display_name,
            sources=source_list,
            products=None,
            created_by_id=None,
            updated=None,
        )

    @log_method
    def create_secret_revision(
        self,
        file_path: PathLike,
        sources: list[NewSource | str] | None = None,
        display_name: str | None = None,
        description: str | None = None,
        version_name: str | None = None,
        external_identifier: str | None = None,
    ) -> FileRevision:
        """
        Create a new secret file revision from a given file path and optional metadata.

        This method uploads a file containing secret content as a new revision, and optionally
        associates metadata such as sources, display name, description, version name, and an external identifier.

        :param file_path: Filesystem path to the file being uploaded as a secret revision.
        :type file_path: PathLike
        :param sources: Optional list of sources associated with the revision. Each item may be a
                        NewSource instance or a string source name.
        :type sources: list[NewSource | str] | None
        :param display_name: Optional human-readable name for the revision.
        :type display_name: str | None
        :param description: Optional text describing the revision's contents or purpose.
        :type description: str | None
        :param version_name: Optional user-defined version label for the revision.
        :type version_name: str | None
        :param external_identifier: Optional external system identifier for the revision.
        :type external_identifier: str | None
        :return: Created secret file revision object
        :rtype: FileRevision
        """

        file_path = Path(file_path)

        # Create properties (same as regular revision)
        properties = Properties.from_path(
            path=file_path,
            description=description,
            version_name=version_name,
            external_identifier=external_identifier,
            display_name=display_name,
        )
        properties_bytes = properties.to_bytes()

        # Read and encrypt content
        with open(file_path, 'rb') as f:
            file_bytes = f.read()

        # Get public key and encrypt content
        tenant_public_key = self.get_tenant_public_key()
        public_key_bytes = tenant_public_key.public_key.encode('utf-8')
        encrypted_content = istari_digital_core.encrypt(public_key_bytes, file_bytes)

        # Create tokens from encrypted content (following Rust pattern)
        content_token = Token.from_bytes(encrypted_content)
        properties_token = Token.from_bytes(properties_bytes, content_token.salt)

        # Upload encrypted content - conditional based on file size
        if properties.size <= self.config.multipart_threshold:  # type: ignore[operator]
            # Small file - use single upload
            content_token = self._upload_content_single(encrypted_content)
        else:
            # Large file - use multipart upload
            content_token = self._upload_content_multipart(encrypted_content)

        # Upload properties
        self._upload_properties(properties_token, properties_bytes)

        # Create sources list (same as regular revision)
        source_list = [
            Source(
                revision_id=source if isinstance(source, str) else source.revision_id,
                file_id=None,
                resource_type=None,
                resource_id=None,
                relationship_identifier=None if isinstance(source, str) else source.relationship_identifier,
            )
            for source in (sources or [])
        ]

        # Create FileRevision (same as regular revision)
        file_revision_id = str(uuid.uuid4())

        file_revision_archive_status = FileRevisionArchiveStatus(
            id=str(uuid.uuid4()),
            created=datetime.now(timezone.utc),
            name=ArchiveStatusName.ACTIVE,
            reason="Initial",
            created_by_id=None,
            file_revision_id=file_revision_id,
        )

        return FileRevision(
            id=file_revision_id,
            created=datetime.now(timezone.utc),
            file_id=None,
            content_token=content_token,
            properties_token=properties_token,
            archive_status_history=[file_revision_archive_status],
            name=properties.file_name,
            extension=properties.extension,
            size=properties.size,
            description=properties.description,
            mime=properties.mime,
            version_name=properties.version_name,
            external_identifier=properties.external_identifier,
            display_name=properties.display_name,
            sources=source_list,
            products=None,
            created_by_id=None,
            updated=None,
        )

    @log_method
    def update_revision_content(
        self,
        file_path: PathLike,
        salt: str,
        sources: list[NewSource | str] | None = None,
        display_name: str | None = None,
        description: str | None = None,
        version_name: str | None = None,
        external_identifier: str | None = None,
    ) -> FileRevision:
        """
        Update the contents of an existing file revision.

        This method uploads new content from the given file path to update an existing revision.
        A salt value must be provided to locate or identify the revision being updated.
        Optional metadata such as sources, display name, description, version name, and
        external identifier can also be modified.

        :param file_path: Filesystem path to the new file content for the revision.
        :type file_path: PathLike
        :param salt: Unique salt value used to identify or deduplicate the revision update.
        :type salt: str
        :param sources: Optional list of sources associated with the revision. Each item may be a
                        NewSource instance or a string source name.
        :type sources: list[NewSource | str] | None
        :param display_name: Optional human-readable name for the revision.
        :type display_name: str | None
        :param description: Optional text describing the updated revision content.
        :type description: str | None
        :param version_name: Optional user-defined version label for the revision.
        :type version_name: str | None
        :param external_identifier: Optional external system identifier for the revision.
        :type external_identifier: str | None
        :return: Updated file revision object
        :rtype: FileRevision
        """

        file_path = Path(file_path)

        # Create properties
        properties = Properties.from_path(
            path=file_path,
            description=description,
            version_name=version_name,
            external_identifier=external_identifier,
            display_name=display_name,
        )
        properties_bytes = properties.to_bytes()

        # Create properties token using the provided salt
        properties_token = Token.from_bytes(properties_bytes, salt)

        # Read file content
        with open(file_path, 'rb') as f:
            file_bytes = f.read()

        # Upload content - conditional based on file size, now using existing methods with salt
        if properties.size <= self.config.multipart_threshold:  # type: ignore[operator]
            # Small file - use single upload with existing salt
            content_token = self._upload_content_single(file_bytes, salt)
        else:
            # Large file - use multipart upload with existing salt
            content_token = self._upload_content_multipart(file_bytes, salt)

        # Upload properties
        self._upload_properties(properties_token, properties_bytes)

        # Create sources list
        source_list = [
            Source(
                revision_id=source if isinstance(source, str) else source.revision_id,
                file_id=None,
                resource_type=None,
                resource_id=None,
                relationship_identifier=None if isinstance(source, str) else source.relationship_identifier,
            )
            for source in (sources or [])
        ]

        # Create FileRevision
        file_revision_id = str(uuid.uuid4())

        file_revision_archive_status = FileRevisionArchiveStatus(
            id=str(uuid.uuid4()),
            created=datetime.now(timezone.utc),
            name=ArchiveStatusName.ACTIVE,
            reason="Initial",
            created_by_id=None,
            file_revision_id=file_revision_id,
        )

        return FileRevision(
            id=file_revision_id,
            created=datetime.now(timezone.utc),
            file_id=None,
            content_token=content_token,
            properties_token=properties_token,
            archive_status_history=[file_revision_archive_status],
            name=properties.file_name,
            extension=properties.extension,
            size=properties.size,
            description=properties.description,
            mime=properties.mime,
            version_name=properties.version_name,
            external_identifier=properties.external_identifier,
            display_name=properties.display_name,
            sources=source_list,
            products=None,
            created_by_id=None,
            updated=None,
        )

    @log_method
    def update_revision_properties(
        self,
        file_revision: FileRevision,
        display_name: str | None = None,
        description: str | None = None,
        external_identifier: str | None = None,
        version_name: str | None = None,
    ) -> TokenWithProperties:
        """
        Update metadata properties of an existing file revision.

        This method modifies the metadata associated with a file revision, such as display name,
        description, version name, or external identifier. The revision content itself is not changed.

        :param file_revision: The file revision to update properties for
        :type file_revision: FileRevision
        :param display_name: Optional new human-readable name for the revision
        :type display_name: str | None
        :param description: Optional new text describing the revision
        :type description: str | None
        :param external_identifier: Optional new external system identifier
        :type external_identifier: str | None
        :param version_name: Optional new user-defined version label
        :type version_name: str | None
        :return: Updated file object
        :rtype: File
        :raises ValueError: If file revision doesn't have a file_id
        """

        # Create updated properties using direct field assignment (no path needed)
        updated_properties = Properties(
            file_name=file_revision.name or "",
            size=file_revision.size or 0,
            extension=file_revision.extension or "",
            mime=file_revision.mime,
            description=description if description is not None else file_revision.description,
            version_name=version_name if version_name is not None else file_revision.version_name,
            external_identifier=external_identifier if external_identifier is not None else file_revision.external_identifier,
            display_name=display_name if display_name is not None else file_revision.display_name,
        )

        # Serialize properties to bytes
        properties_bytes = updated_properties.to_bytes()

        # Create properties token using existing salt (key part - maintains identity)
        properties_token = Token.from_bytes(properties_bytes, file_revision.properties_token.salt)

        # Upload updated properties
        self._upload_properties(properties_token, properties_bytes)

        # Create TokenWithProperties from the updated token and properties
        return TokenWithProperties(
            id=str(uuid.uuid4()),
            created=datetime.now(timezone.utc),
            sha=properties_token.sha,
            salt=properties_token.salt,
            name=updated_properties.file_name,
            extension=updated_properties.extension,
            size=updated_properties.size,
            description=updated_properties.description,
            mime=updated_properties.mime,
            version_name=updated_properties.version_name,
            external_identifier=updated_properties.external_identifier,
            display_name=updated_properties.display_name,
        )


    @validate_call
    @log_method
    def create_multipart_upload(
        self,
        create_multipart_upload_request: CreateMultipartUploadRequest,
        http_request_timeout_secs: Annotated[StrictInt | None, Field(ge=0)] = None,
        _request_auth: Optional[Dict[StrictStr, Any]] = None,
        _content_type: Optional[StrictStr] = None,
        _headers: Optional[Dict[StrictStr, Any]] = None,
        _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0
    ) -> CreateMultipartUploadResponse:

        """
        Returns the metadata needed to perform a multipart upload for a given token with the SHA

        :param create_multipart_upload_request: (required)
        :type create_multipart_upload_request: CreateMultipartUploadRequest
        :param http_request_timeout_secs: timeout setting for this request
        :type http_request_timeout_secs: int, optional

        """ # noqa: E501

        _param = self._create_multipart_upload_serialize(
            create_multipart_upload_request=create_multipart_upload_request,
            _request_auth=_request_auth,
            _content_type=_content_type,
            _headers=_headers,
            _host_index=_host_index
        )

        _response_types_map: Dict[str, Optional[str]] = {
            '200': "CreateMultipartUploadResponse",
            '422': "HTTPValidationError",
        }

        request_timeout = (
            http_request_timeout_secs if http_request_timeout_secs is not None
            else self.config.http_request_timeout_secs
        )
        response_data = self.api_client.call_api(
            *_param,
            _request_timeout=request_timeout,
        )
        response_data.read()

        resp_obj: CreateMultipartUploadResponse = self.api_client.response_deserialize(
            response_data=response_data,
            response_types_map=_response_types_map,
        ).data

        return resp_obj

    def _create_multipart_upload_serialize(
        self,
        create_multipart_upload_request,
        _request_auth,
        _content_type,
        _headers,
        _host_index,
    ) -> RequestSerialized:

        _host = None

        _collection_formats: Dict[str, str] = {
        }

        _path_params: Dict[str, str] = {}
        _query_params: List[Tuple[str, str]] = []
        _header_params: Dict[str, Optional[str]] = _headers or {}
        _form_params: List[Tuple[str, str]] = []
        _files: Dict[
            str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]]
        ] = {}
        _body_params: Optional[bytes] = None

        # process the path parameters
        # process the query parameters
        # process the header parameters
        # process the form parameters
        # process the body parameter
        if create_multipart_upload_request is not None:
            _body_params = create_multipart_upload_request


        # set the HTTP header `Accept`
        if 'Accept' not in _header_params:
            _header_params['Accept'] = self.api_client.select_header_accept(
                [
                    'application/json'
                ]
            )

        # set the HTTP header `Content-Type`
        if _content_type:
            _header_params['Content-Type'] = _content_type
        else:
            _default_content_type = (
                self.api_client.select_header_content_type(
                    [
                        'application/json'
                    ]
                )
            )
            if _default_content_type is not None:
                _header_params['Content-Type'] = _default_content_type

        # authentication setting
        _auth_settings: List[str] = [
            'RequestAuthenticator'
        ]

        return self.api_client.param_serialize(
            method='POST',
            resource_path='/api/v2/tokens/multipart-upload',
            path_params=_path_params,
            query_params=_query_params,
            header_params=_header_params,
            body=_body_params,
            post_params=_form_params,
            files=_files,
            auth_settings=_auth_settings,
            collection_formats=_collection_formats,
            _host=_host,
            _request_auth=_request_auth
        )

    @validate_call
    @log_method
    def create_tenant_public_key(
        self,
        public_key_file: Union[StrictBytes, StrictStr, Tuple[StrictStr, StrictBytes]],
        http_request_timeout_secs: Annotated[StrictInt | None, Field(ge=0)] = None,
        _request_auth: Optional[Dict[StrictStr, Any]] = None,
        _content_type: Optional[StrictStr] = None,
        _headers: Optional[Dict[StrictStr, Any]] = None,
        _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0
    ) -> TenantPublicKey:

        """
        Creates a tenant public key.

        :param public_key_file: (required)
        :type public_key_file: bytearray
        :param http_request_timeout_secs: timeout setting for this request
        :type http_request_timeout_secs: int, optional

        """ # noqa: E501

        _param = self._create_tenant_public_key_serialize(
            public_key_file=public_key_file,
            _request_auth=_request_auth,
            _content_type=_content_type,
            _headers=_headers,
            _host_index=_host_index
        )

        _response_types_map: Dict[str, Optional[str]] = {
            '201': "TenantPublicKey",
            '422': "HTTPValidationError",
        }

        request_timeout = (
            http_request_timeout_secs if http_request_timeout_secs is not None
            else self.config.http_request_timeout_secs
        )
        response_data = self.api_client.call_api(
            *_param,
            _request_timeout=request_timeout,
        )
        response_data.read()

        resp_obj: TenantPublicKey = self.api_client.response_deserialize(
            response_data=response_data,
            response_types_map=_response_types_map,
        ).data

        return resp_obj

    def _create_tenant_public_key_serialize(
        self,
        public_key_file,
        _request_auth,
        _content_type,
        _headers,
        _host_index,
    ) -> RequestSerialized:

        _host = None

        _collection_formats: Dict[str, str] = {
        }

        _path_params: Dict[str, str] = {}
        _query_params: List[Tuple[str, str]] = []
        _header_params: Dict[str, Optional[str]] = _headers or {}
        _form_params: List[Tuple[str, str]] = []
        _files: Dict[
            str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]]
        ] = {}
        _body_params: Optional[bytes] = None

        # process the path parameters
        # process the query parameters
        # process the header parameters
        # process the form parameters
        if public_key_file is not None:
            _files['public_key_file'] = public_key_file
        # process the body parameter


        # set the HTTP header `Accept`
        if 'Accept' not in _header_params:
            _header_params['Accept'] = self.api_client.select_header_accept(
                [
                    'application/json'
                ]
            )

        # set the HTTP header `Content-Type`
        if _content_type:
            _header_params['Content-Type'] = _content_type
        else:
            _default_content_type = (
                self.api_client.select_header_content_type(
                    [
                        'multipart/form-data'
                    ]
                )
            )
            if _default_content_type is not None:
                _header_params['Content-Type'] = _default_content_type

        # authentication setting
        _auth_settings: List[str] = [
            'RequestAuthenticator'
        ]

        return self.api_client.param_serialize(
            method='POST',
            resource_path='/api/v2/tenant/publickey',
            path_params=_path_params,
            query_params=_query_params,
            header_params=_header_params,
            body=_body_params,
            post_params=_form_params,
            files=_files,
            auth_settings=_auth_settings,
            collection_formats=_collection_formats,
            _host=_host,
            _request_auth=_request_auth
        )

    @validate_call
    @log_method
    def delete_tenant_public_key(
        self,
        http_request_timeout_secs: Annotated[StrictInt | None, Field(ge=0)] = None,
        _request_auth: Optional[Dict[StrictStr, Any]] = None,
        _content_type: Optional[StrictStr] = None,
        _headers: Optional[Dict[StrictStr, Any]] = None,
        _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0
    ) -> None:

        """
        Deletes a tenant public key.

        :param http_request_timeout_secs: timeout setting for this request
        :type http_request_timeout_secs: int, optional

        """ # noqa: E501

        _param = self._delete_tenant_public_key_serialize(
            _request_auth=_request_auth,
            _content_type=_content_type,
            _headers=_headers,
            _host_index=_host_index
        )

        _response_types_map: Dict[str, Optional[str]] = {
            '204': None,
        }

        request_timeout = (
            http_request_timeout_secs if http_request_timeout_secs is not None
            else self.config.http_request_timeout_secs
        )
        response_data = self.api_client.call_api(
            *_param,
            _request_timeout=request_timeout,
        )
        response_data.read()

        resp_obj: None = self.api_client.response_deserialize(
            response_data=response_data,
            response_types_map=_response_types_map,
        ).data

        return resp_obj

    def _delete_tenant_public_key_serialize(
        self,
        _request_auth,
        _content_type,
        _headers,
        _host_index,
    ) -> RequestSerialized:

        _host = None

        _collection_formats: Dict[str, str] = {
        }

        _path_params: Dict[str, str] = {}
        _query_params: List[Tuple[str, str]] = []
        _header_params: Dict[str, Optional[str]] = _headers or {}
        _form_params: List[Tuple[str, str]] = []
        _files: Dict[
            str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]]
        ] = {}
        _body_params: Optional[bytes] = None

        # process the path parameters
        # process the query parameters
        # process the header parameters
        # process the form parameters
        # process the body parameter




        # authentication setting
        _auth_settings: List[str] = [
            'RequestAuthenticator'
        ]

        return self.api_client.param_serialize(
            method='DELETE',
            resource_path='/api/v2/tenant/publickey',
            path_params=_path_params,
            query_params=_query_params,
            header_params=_header_params,
            body=_body_params,
            post_params=_form_params,
            files=_files,
            auth_settings=_auth_settings,
            collection_formats=_collection_formats,
            _host=_host,
            _request_auth=_request_auth
        )

    @validate_call
    @log_method
    def finalize_multipart_upload(
        self,
        finalize_multipart_upload_request: FinalizeMultipartUploadRequest,
        http_request_timeout_secs: Annotated[StrictInt | None, Field(ge=0)] = None,
        _request_auth: Optional[Dict[StrictStr, Any]] = None,
        _content_type: Optional[StrictStr] = None,
        _headers: Optional[Dict[StrictStr, Any]] = None,
        _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0
    ) -> object:

        """
        Finalize a multipart upload for a given token with the SHA

        :param finalize_multipart_upload_request: (required)
        :type finalize_multipart_upload_request: FinalizeMultipartUploadRequest
        :param http_request_timeout_secs: timeout setting for this request
        :type http_request_timeout_secs: int, optional

        """ # noqa: E501

        _param = self._finalize_multipart_upload_serialize(
            finalize_multipart_upload_request=finalize_multipart_upload_request,
            _request_auth=_request_auth,
            _content_type=_content_type,
            _headers=_headers,
            _host_index=_host_index
        )

        _response_types_map: Dict[str, Optional[str]] = {
            '200': "object",
            '422': "HTTPValidationError",
        }

        request_timeout = (
            http_request_timeout_secs if http_request_timeout_secs is not None
            else self.config.http_request_timeout_secs
        )
        response_data = self.api_client.call_api(
            *_param,
            _request_timeout=request_timeout,
        )
        response_data.read()

        resp_obj: object = self.api_client.response_deserialize(
            response_data=response_data,
            response_types_map=_response_types_map,
        ).data

        return resp_obj

    def _finalize_multipart_upload_serialize(
        self,
        finalize_multipart_upload_request,
        _request_auth,
        _content_type,
        _headers,
        _host_index,
    ) -> RequestSerialized:

        _host = None

        _collection_formats: Dict[str, str] = {
        }

        _path_params: Dict[str, str] = {}
        _query_params: List[Tuple[str, str]] = []
        _header_params: Dict[str, Optional[str]] = _headers or {}
        _form_params: List[Tuple[str, str]] = []
        _files: Dict[
            str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]]
        ] = {}
        _body_params: Optional[bytes] = None

        # process the path parameters
        # process the query parameters
        # process the header parameters
        # process the form parameters
        # process the body parameter
        if finalize_multipart_upload_request is not None:
            _body_params = finalize_multipart_upload_request


        # set the HTTP header `Accept`
        if 'Accept' not in _header_params:
            _header_params['Accept'] = self.api_client.select_header_accept(
                [
                    'application/json'
                ]
            )

        # set the HTTP header `Content-Type`
        if _content_type:
            _header_params['Content-Type'] = _content_type
        else:
            _default_content_type = (
                self.api_client.select_header_content_type(
                    [
                        'application/json'
                    ]
                )
            )
            if _default_content_type is not None:
                _header_params['Content-Type'] = _default_content_type

        # authentication setting
        _auth_settings: List[str] = [
            'RequestAuthenticator'
        ]

        return self.api_client.param_serialize(
            method='PUT',
            resource_path='/api/v2/tokens/multipart-upload',
            path_params=_path_params,
            query_params=_query_params,
            header_params=_header_params,
            body=_body_params,
            post_params=_form_params,
            files=_files,
            auth_settings=_auth_settings,
            collection_formats=_collection_formats,
            _host=_host,
            _request_auth=_request_auth
        )

    @validate_call
    @log_method
    def generate_download_url(
        self,
        sha: StrictStr,
        http_request_timeout_secs: Annotated[StrictInt | None, Field(ge=0)] = None,
        _request_auth: Optional[Dict[StrictStr, Any]] = None,
        _content_type: Optional[StrictStr] = None,
        _headers: Optional[Dict[StrictStr, Any]] = None,
        _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0
    ) -> TemporaryURL:

        """
        Returns a temporary download url for tokenized resource with SHA value

        :param sha: (required)
        :type sha: str
        :param http_request_timeout_secs: timeout setting for this request
        :type http_request_timeout_secs: int, optional

        """ # noqa: E501

        _param = self._generate_download_url_serialize(
            sha=sha,
            _request_auth=_request_auth,
            _content_type=_content_type,
            _headers=_headers,
            _host_index=_host_index
        )

        _response_types_map: Dict[str, Optional[str]] = {
            '200': "TemporaryURL",
            '422': "HTTPValidationError",
        }

        request_timeout = (
            http_request_timeout_secs if http_request_timeout_secs is not None
            else self.config.http_request_timeout_secs
        )
        response_data = self.api_client.call_api(
            *_param,
            _request_timeout=request_timeout,
        )
        response_data.read()

        resp_obj: TemporaryURL = self.api_client.response_deserialize(
            response_data=response_data,
            response_types_map=_response_types_map,
        ).data

        return resp_obj

    def _generate_download_url_serialize(
        self,
        sha,
        _request_auth,
        _content_type,
        _headers,
        _host_index,
    ) -> RequestSerialized:

        _host = None

        _collection_formats: Dict[str, str] = {
        }

        _path_params: Dict[str, str] = {}
        _query_params: List[Tuple[str, str]] = []
        _header_params: Dict[str, Optional[str]] = _headers or {}
        _form_params: List[Tuple[str, str]] = []
        _files: Dict[
            str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]]
        ] = {}
        _body_params: Optional[bytes] = None

        # process the path parameters
        if sha is not None:
            _path_params['sha'] = sha
        # process the query parameters
        # process the header parameters
        # process the form parameters
        # process the body parameter


        # set the HTTP header `Accept`
        if 'Accept' not in _header_params:
            _header_params['Accept'] = self.api_client.select_header_accept(
                [
                    'application/json'
                ]
            )


        # authentication setting
        _auth_settings: List[str] = [
            'RequestAuthenticator'
        ]

        return self.api_client.param_serialize(
            method='GET',
            resource_path='/api/v2/tokens/{sha}/download-url',
            path_params=_path_params,
            query_params=_query_params,
            header_params=_header_params,
            body=_body_params,
            post_params=_form_params,
            files=_files,
            auth_settings=_auth_settings,
            collection_formats=_collection_formats,
            _host=_host,
            _request_auth=_request_auth
        )

    @validate_call
    @log_method
    def generate_upload_url(
        self,
        sha: StrictStr,
        model_id: Optional[StrictStr] = None,
        http_request_timeout_secs: Annotated[StrictInt | None, Field(ge=0)] = None,
        _request_auth: Optional[Dict[StrictStr, Any]] = None,
        _content_type: Optional[StrictStr] = None,
        _headers: Optional[Dict[StrictStr, Any]] = None,
        _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0
    ) -> TemporaryURL:

        """
        Returns a temporary upload url for token with sha

        :param sha: (required)
        :type sha: str
        :param model_id:
        :type model_id: str
        :param http_request_timeout_secs: timeout setting for this request
        :type http_request_timeout_secs: int, optional

        """ # noqa: E501

        _param = self._generate_upload_url_serialize(
            sha=sha,
            model_id=model_id,
            _request_auth=_request_auth,
            _content_type=_content_type,
            _headers=_headers,
            _host_index=_host_index
        )

        _response_types_map: Dict[str, Optional[str]] = {
            '200': "TemporaryURL",
            '422': "HTTPValidationError",
        }

        request_timeout = (
            http_request_timeout_secs if http_request_timeout_secs is not None
            else self.config.http_request_timeout_secs
        )
        response_data = self.api_client.call_api(
            *_param,
            _request_timeout=request_timeout,
        )
        response_data.read()

        resp_obj: TemporaryURL = self.api_client.response_deserialize(
            response_data=response_data,
            response_types_map=_response_types_map,
        ).data

        return resp_obj

    def _generate_upload_url_serialize(
        self,
        sha,
        model_id,
        _request_auth,
        _content_type,
        _headers,
        _host_index,
    ) -> RequestSerialized:

        _host = None

        _collection_formats: Dict[str, str] = {
        }

        _path_params: Dict[str, str] = {}
        _query_params: List[Tuple[str, str]] = []
        _header_params: Dict[str, Optional[str]] = _headers or {}
        _form_params: List[Tuple[str, str]] = []
        _files: Dict[
            str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]]
        ] = {}
        _body_params: Optional[bytes] = None

        # process the path parameters
        if sha is not None:
            _path_params['sha'] = sha
        # process the query parameters
        if model_id is not None:

            _query_params.append(('model_id', model_id))

        # process the header parameters
        # process the form parameters
        # process the body parameter


        # set the HTTP header `Accept`
        if 'Accept' not in _header_params:
            _header_params['Accept'] = self.api_client.select_header_accept(
                [
                    'application/json'
                ]
            )


        # authentication setting
        _auth_settings: List[str] = [
            'RequestAuthenticator'
        ]

        return self.api_client.param_serialize(
            method='GET',
            resource_path='/api/v2/tokens/{sha}/upload-url',
            path_params=_path_params,
            query_params=_query_params,
            header_params=_header_params,
            body=_body_params,
            post_params=_form_params,
            files=_files,
            auth_settings=_auth_settings,
            collection_formats=_collection_formats,
            _host=_host,
            _request_auth=_request_auth
        )

    @validate_call
    @log_method
    def get_tenant_public_key(
        self,
        http_request_timeout_secs: Annotated[StrictInt | None, Field(ge=0)] = None,
        _request_auth: Optional[Dict[StrictStr, Any]] = None,
        _content_type: Optional[StrictStr] = None,
        _headers: Optional[Dict[StrictStr, Any]] = None,
        _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0
    ) -> TenantPublicKey:

        """
        Gets a tenant public key.

        :param http_request_timeout_secs: timeout setting for this request
        :type http_request_timeout_secs: int, optional

        """ # noqa: E501

        _param = self._get_tenant_public_key_serialize(
            _request_auth=_request_auth,
            _content_type=_content_type,
            _headers=_headers,
            _host_index=_host_index
        )

        _response_types_map: Dict[str, Optional[str]] = {
            '200': "TenantPublicKey",
        }

        request_timeout = (
            http_request_timeout_secs if http_request_timeout_secs is not None
            else self.config.http_request_timeout_secs
        )
        response_data = self.api_client.call_api(
            *_param,
            _request_timeout=request_timeout,
        )
        response_data.read()

        resp_obj: TenantPublicKey = self.api_client.response_deserialize(
            response_data=response_data,
            response_types_map=_response_types_map,
        ).data

        return resp_obj

    def _get_tenant_public_key_serialize(
        self,
        _request_auth,
        _content_type,
        _headers,
        _host_index,
    ) -> RequestSerialized:

        _host = None

        _collection_formats: Dict[str, str] = {
        }

        _path_params: Dict[str, str] = {}
        _query_params: List[Tuple[str, str]] = []
        _header_params: Dict[str, Optional[str]] = _headers or {}
        _form_params: List[Tuple[str, str]] = []
        _files: Dict[
            str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]]
        ] = {}
        _body_params: Optional[bytes] = None

        # process the path parameters
        # process the query parameters
        # process the header parameters
        # process the form parameters
        # process the body parameter


        # set the HTTP header `Accept`
        if 'Accept' not in _header_params:
            _header_params['Accept'] = self.api_client.select_header_accept(
                [
                    'application/json'
                ]
            )


        # authentication setting
        _auth_settings: List[str] = [
            'RequestAuthenticator'
        ]

        return self.api_client.param_serialize(
            method='GET',
            resource_path='/api/v2/tenant/publickey',
            path_params=_path_params,
            query_params=_query_params,
            header_params=_header_params,
            body=_body_params,
            post_params=_form_params,
            files=_files,
            auth_settings=_auth_settings,
            collection_formats=_collection_formats,
            _host=_host,
            _request_auth=_request_auth
        )

    @validate_call
    @log_method
    def list_tenants(
        self,
        page: Annotated[Optional[Annotated[int, Field(strict=True, ge=1)]], Field(description="Page number")] = None,
        size: Annotated[Optional[Annotated[int, Field(le=100, strict=True, ge=1)]], Field(description="Page size")] = None,
        http_request_timeout_secs: Annotated[StrictInt | None, Field(ge=0)] = None,
        _request_auth: Optional[Dict[StrictStr, Any]] = None,
        _content_type: Optional[StrictStr] = None,
        _headers: Optional[Dict[StrictStr, Any]] = None,
        _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0
    ) -> PageTenant:

        """
        This method lists all tenants (admin only).

        :param page: Page number
        :type page: int
        :param size: Page size
        :type size: int
        :param http_request_timeout_secs: timeout setting for this request
        :type http_request_timeout_secs: int, optional

        """ # noqa: E501

        _param = self._list_tenants_serialize(
            page=page,
            size=size,
            _request_auth=_request_auth,
            _content_type=_content_type,
            _headers=_headers,
            _host_index=_host_index
        )

        _response_types_map: Dict[str, Optional[str]] = {
            '200': "PageTenant",
            '422': "HTTPValidationError",
        }

        request_timeout = (
            http_request_timeout_secs if http_request_timeout_secs is not None
            else self.config.http_request_timeout_secs
        )
        response_data = self.api_client.call_api(
            *_param,
            _request_timeout=request_timeout,
        )
        response_data.read()

        resp_obj: PageTenant = self.api_client.response_deserialize(
            response_data=response_data,
            response_types_map=_response_types_map,
        ).data

        return resp_obj

    def _list_tenants_serialize(
        self,
        page,
        size,
        _request_auth,
        _content_type,
        _headers,
        _host_index,
    ) -> RequestSerialized:

        _host = None

        _collection_formats: Dict[str, str] = {
        }

        _path_params: Dict[str, str] = {}
        _query_params: List[Tuple[str, str]] = []
        _header_params: Dict[str, Optional[str]] = _headers or {}
        _form_params: List[Tuple[str, str]] = []
        _files: Dict[
            str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]]
        ] = {}
        _body_params: Optional[bytes] = None

        # process the path parameters
        # process the query parameters
        if page is not None:

            _query_params.append(('page', page))

        if size is not None:

            _query_params.append(('size', size))

        # process the header parameters
        # process the form parameters
        # process the body parameter


        # set the HTTP header `Accept`
        if 'Accept' not in _header_params:
            _header_params['Accept'] = self.api_client.select_header_accept(
                [
                    'application/json'
                ]
            )


        # authentication setting
        _auth_settings: List[str] = [
            'RequestAuthenticator'
        ]

        return self.api_client.param_serialize(
            method='GET',
            resource_path='/api/v2/tenants',
            path_params=_path_params,
            query_params=_query_params,
            header_params=_header_params,
            body=_body_params,
            post_params=_form_params,
            files=_files,
            auth_settings=_auth_settings,
            collection_formats=_collection_formats,
            _host=_host,
            _request_auth=_request_auth
        )

    @validate_call
    @log_method
    def update_tenant_public_key(
        self,
        public_key_file: Union[StrictBytes, StrictStr, Tuple[StrictStr, StrictBytes]],
        http_request_timeout_secs: Annotated[StrictInt | None, Field(ge=0)] = None,
        _request_auth: Optional[Dict[StrictStr, Any]] = None,
        _content_type: Optional[StrictStr] = None,
        _headers: Optional[Dict[StrictStr, Any]] = None,
        _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0
    ) -> TenantPublicKey:

        """
        Updates a tenant public key.

        :param public_key_file: (required)
        :type public_key_file: bytearray
        :param http_request_timeout_secs: timeout setting for this request
        :type http_request_timeout_secs: int, optional

        """ # noqa: E501

        _param = self._update_tenant_public_key_serialize(
            public_key_file=public_key_file,
            _request_auth=_request_auth,
            _content_type=_content_type,
            _headers=_headers,
            _host_index=_host_index
        )

        _response_types_map: Dict[str, Optional[str]] = {
            '200': "TenantPublicKey",
            '422': "HTTPValidationError",
        }

        request_timeout = (
            http_request_timeout_secs if http_request_timeout_secs is not None
            else self.config.http_request_timeout_secs
        )
        response_data = self.api_client.call_api(
            *_param,
            _request_timeout=request_timeout,
        )
        response_data.read()

        resp_obj: TenantPublicKey = self.api_client.response_deserialize(
            response_data=response_data,
            response_types_map=_response_types_map,
        ).data

        return resp_obj

    def _update_tenant_public_key_serialize(
        self,
        public_key_file,
        _request_auth,
        _content_type,
        _headers,
        _host_index,
    ) -> RequestSerialized:

        _host = None

        _collection_formats: Dict[str, str] = {
        }

        _path_params: Dict[str, str] = {}
        _query_params: List[Tuple[str, str]] = []
        _header_params: Dict[str, Optional[str]] = _headers or {}
        _form_params: List[Tuple[str, str]] = []
        _files: Dict[
            str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]]
        ] = {}
        _body_params: Optional[bytes] = None

        # process the path parameters
        # process the query parameters
        # process the header parameters
        # process the form parameters
        if public_key_file is not None:
            _files['public_key_file'] = public_key_file
        # process the body parameter


        # set the HTTP header `Accept`
        if 'Accept' not in _header_params:
            _header_params['Accept'] = self.api_client.select_header_accept(
                [
                    'application/json'
                ]
            )

        # set the HTTP header `Content-Type`
        if _content_type:
            _header_params['Content-Type'] = _content_type
        else:
            _default_content_type = (
                self.api_client.select_header_content_type(
                    [
                        'multipart/form-data'
                    ]
                )
            )
            if _default_content_type is not None:
                _header_params['Content-Type'] = _default_content_type

        # authentication setting
        _auth_settings: List[str] = [
            'RequestAuthenticator'
        ]

        return self.api_client.param_serialize(
            method='PUT',
            resource_path='/api/v2/tenant/publickey',
            path_params=_path_params,
            query_params=_query_params,
            header_params=_header_params,
            body=_body_params,
            post_params=_form_params,
            files=_files,
            auth_settings=_auth_settings,
            collection_formats=_collection_formats,
            _host=_host,
            _request_auth=_request_auth
        )
