# 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, List, 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
from istari_digital_client.v2.models.revision_bulk_create_item import RevisionBulkCreateItem

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, *, upload_url: TemporaryURL | None = None) -> 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) if not upload_url else upload_url
            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,  *, upload_url: TemporaryURL | 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) if not upload_url else upload_url
            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_revisions_bulk(
        self,
        revision_items: list[RevisionBulkCreateItem]
    ) -> list[FileRevision]:
        """
        Create multiple file revisions in a single bulk operation.

        This method uploads multiple files as new revisions, processing their properties
        and content in batches for improved efficiency. Each revision item can include
        optional metadata such as sources, display name, description, version name,
        and an external identifier.

        Note: This method does not support files larger than the multipart upload threshold.
        For large files, use `create_revision` individually.

        :param revision_items: List of revision creation items, each specifying a file path
                               and optional metadata (sources, description, version_name,
                               external_identifier, display_name).
        :type revision_items: list[RevisionBulkCreateItem]
        :return: List of created file revision objects
        :rtype: list[FileRevision]
        :raises ValueError: If any file exceeds the multipart upload threshold.
        """
        # Deal with properties
        properties_list: list[Properties] = []
        property_information: list[Tuple[Token, bytes]] = []

        for revision in revision_items:
            properties = Properties.from_path(
                path=Path(revision.path),
                description=revision.description,
                version_name=revision.version_name,
                external_identifier=revision.external_identifier,
                display_name=revision.display_name,
            )
            properties_list.append(properties)

            if properties.size > self.config.multipart_threshold:  # type: ignore[operator]
                raise ValueError("Tried to bulk upload file above multipart threshold.")

            properties_bytes = properties.to_bytes()

            properties_token = Token.from_bytes(properties_bytes)

            property_information.append((properties_token, properties_bytes))

        properties_urls = self.generate_upload_urls_bulk(
            [token[0].sha for token in property_information]
        )

        for index, url in enumerate(properties_urls):
            properties_token, properties_bytes = property_information[index]
            # Upload properties
            self._upload_properties(properties_token, properties_bytes, upload_url=url)


        # Deal with content
        content_information: list[Tuple[Token, bytes]] = []

        for revision in revision_items:
            # Read file content
            with open(revision.path, 'rb') as f:
                file_bytes = f.read()

            content_token = Token.from_bytes(file_bytes)

            content_information.append((content_token, file_bytes))

        content_urls = self.generate_upload_urls_bulk(
            [token[0].sha for token in content_information]
        )

        for index, url in enumerate(content_urls):
            content_token, content_bytes = content_information[index]
            # Upload content, passing salt so that we get the same shas as we already calculated
            content_token = self._upload_content_single(content_bytes, salt=content_token.salt, upload_url=url)
            content_information[index] = (content_token, content_bytes)  # overwrite token to make sure it matches

        file_revisions: list[FileRevision] = []

        for index, revision in enumerate(revision_items):
            # 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 (revision.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,
            )

            properties = properties_list[index]
            content_token = content_information[index][0]
            properties_token = property_information[index][0]

            file_revision =  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,
            )

            file_revisions.append(file_revision)

        return file_revisions

    @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 generate_upload_urls_bulk(
        self,
        request_body: List[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
    ) -> List[TemporaryURL]:

        """
        Returns temporary upload urls for each sha provided

        :param request_body: (required)
        :type request_body: List[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_urls_bulk_serialize(
            request_body=request_body,
            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': "List[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: List[TemporaryURL] = self.api_client.response_deserialize(
            response_data=response_data,
            response_types_map=_response_types_map,
        ).data

        return resp_obj

    def _generate_upload_urls_bulk_serialize(
        self,
        request_body,
        model_id,
        _request_auth,
        _content_type,
        _headers,
        _host_index,
    ) -> RequestSerialized:

        _host = None

        _collection_formats: Dict[str, str] = {
            'request_body': '',
        }

        _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 model_id is not None:

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

        # process the header parameters
        # process the form parameters
        # process the body parameter
        if request_body is not None:
            _body_params = request_body


        # 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/upload-urls_bulk',
            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
        )
