"""Materialize opaque Agent runtime file handles from the OSS exchange prefix."""

from __future__ import annotations

import os
from pathlib import Path

from arr_processing.errors import ProcessingTransportError
from arr_storage.exchange import OutputExchangeConfig
from arr_storage.remote import CloudClientError, CloudClientPort


class PrefixRemoteFilePort:
    """Resolve an opaque handle only within one controlled exchange prefix."""

    def __init__(self, client: CloudClientPort, config: OutputExchangeConfig) -> None:
        self._client = client
        self._config = config

    def materialize(self, file_handle: str, destination: Path, max_bytes: int) -> None:
        try:
            object_key = self._config.object_key(file_handle)
        except ValueError:
            raise ProcessingTransportError(
                "PROCESSING_ARTIFACT_HANDLE_INVALID", retryable=False
            ) from None
        if not isinstance(max_bytes, int) or isinstance(max_bytes, bool) or max_bytes < 0:
            raise ProcessingTransportError(
                "PROCESSING_ARTIFACT_REQUEST_INVALID", retryable=False
            )
        try:
            descriptor = self._client.stat_object(object_key)
            if descriptor.byte_size > max_bytes:
                raise ProcessingTransportError(
                    "PROCESSING_ARTIFACT_TOO_LARGE", retryable=False
                )
            destination.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
            output = os.open(destination, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
            try:
                copied = 0
                with self._client.stream_object(object_key) as source, os.fdopen(
                    output, "wb"
                ) as target:
                    output = -1
                    while True:
                        chunk = source.read(1024 * 1024)
                        if not chunk:
                            break
                        if not isinstance(chunk, bytes):
                            raise OSError("remote output stream is invalid")
                        copied += len(chunk)
                        if copied > max_bytes:
                            raise ProcessingTransportError(
                                "PROCESSING_ARTIFACT_TOO_LARGE", retryable=False
                            )
                        target.write(chunk)
                    target.flush()
                    os.fsync(target.fileno())
                if copied != descriptor.byte_size:
                    raise OSError("remote output size changed")
            finally:
                if output >= 0:
                    os.close(output)
        except ProcessingTransportError:
            destination.unlink(missing_ok=True)
            raise
        except FileExistsError:
            raise ProcessingTransportError(
                "PROCESSING_ARTIFACT_DESTINATION_CONFLICT", retryable=False
            ) from None
        except CloudClientError as error:
            destination.unlink(missing_ok=True)
            code = {
                "not_found": "PROCESSING_ARTIFACT_NOT_FOUND",
                "forbidden": "PROCESSING_ARTIFACT_FORBIDDEN",
                "invalid_request": "PROCESSING_ARTIFACT_REQUEST_INVALID",
            }.get(error.kind, "PROCESSING_ARTIFACT_UNAVAILABLE")
            raise ProcessingTransportError(
                code,
                retryable=error.kind in {"not_found", "unavailable"},
            ) from None
        except OSError:
            destination.unlink(missing_ok=True)
            raise ProcessingTransportError(
                "PROCESSING_ARTIFACT_UNAVAILABLE", retryable=True
            ) from None

