"""
Protected module contains job response file classes
"""

import os
import tempfile
from enum import Enum
from shutil import copyfile
from typing import Optional


class JobResponseFileType(Enum):
    """
    Enum representing different types of job response file.

        * BYTE_ARRAY if the file is provided as a byte array (in memory)
        * FILE_PATH if the file is stored on a filesystem temp path.
    """

    BYTE_ARRAY = 0
    FILE_PATH = 1


class JobResponseFile:
    """
    Represents a file returned by a job response, stored either in memory or on disk.

    Small files (below the ``file_cache_byte_limit`` threshold used during the job call) are
    kept as a ``bytearray`` in memory (``BYTE_ARRAY``). Larger files are written to a temporary
    path on disk (``FILE_PATH``).

    Args:
        result_file_type (JobResponseFileType): Storage type of the file.
        file_name (str | None): File name (required for ``BYTE_ARRAY`` type).
        byte_array (bytes | None): Raw file bytes (required for ``BYTE_ARRAY`` type).
        file_path (str | None): Absolute path to the temp file (required for ``FILE_PATH`` type).

    Raises:
        ValueError: If the argument combination does not match the given ``result_file_type``.
    """

    def __init__(
        self,
        result_file_type: JobResponseFileType,
        file_name: Optional[str] = None,
        byte_array: Optional[bytes] = None,
        file_path: Optional[str] = None,
    ):
        self.type = result_file_type

        if self.type == JobResponseFileType.BYTE_ARRAY and file_name is not None and byte_array is not None:
            self.name = file_name
            self.__bytes__ = byte_array
            self.__path__ = None

        elif self.type == JobResponseFileType.FILE_PATH and file_path is not None:
            self.name = os.path.basename(file_path)
            self.__bytes__ = None
            self.__path__ = file_path

        else:
            raise ValueError(
                "Invalid parameters provided. For byte array result files, both 'file_name' and 'byte_array' must be provided. "
                "For file path result files, only 'file_path' is required."
            )

        self.extension = os.path.splitext(self.name)[1]

    def size(self) -> int:
        """
        Return the file size in bytes.

        Returns:
            int: Number of bytes in the file, or ``0`` if the data is unavailable.
        """
        if self.type == JobResponseFileType.BYTE_ARRAY and self.__bytes__:
            return len(self.__bytes__)

        if self.type == JobResponseFileType.FILE_PATH and self.__path__:
            return os.path.getsize(self.__path__)

        return 0

    def store(self, path: Optional[str] = None) -> str | None:
        """
        Write the file to disk and return the storage path.

        Args:
            path (str | None): Target file path. When ``None``, the file is saved to the system
                temporary directory. For ``FILE_PATH`` type files, ``None`` returns the existing
                temp path without copying.

        Returns:
            str | None: The path where the file was stored, or ``None`` if no path was produced.

        Raises:
            ValueError: If the internal file data is unexpectedly absent.
        """
        if self.type == JobResponseFileType.BYTE_ARRAY:
            if not self.__bytes__:
                raise ValueError("For data type bytearray self.__bytes__ must be provided.")

            if path is None:
                path = os.path.join(tempfile.gettempdir(), self.name)
            with open(path, "wb") as file:
                file.write(self.__bytes__)
            return path

        if self.type == JobResponseFileType.FILE_PATH:
            if not self.__path__:
                raise ValueError("For data type filepath self.__path__ must be provided.")

            if path is None:
                return self.__path__
            copyfile(self.__path__, path)

        return path

    def bytes(self) -> bytes:
        """
        Return the file content as bytes.

        For ``FILE_PATH`` files, the content is read from disk and cached in memory on first call.

        Returns:
            bytes: The complete file content.

        Raises:
            ValueError: If neither in-memory bytes nor a file path are available.
        """
        if self.__bytes__:
            return self.__bytes__

        if self.__path__:
            with open(self.__path__, "rb") as file_pointer:
                self.__bytes__ = file_pointer.read()
            return self.__bytes__

        raise ValueError("No data available for conversion to bytes.")
