"""Protocol interface for repository item export operations.

This module defines the ExportItemProtocol that repositories can implement
to support exporting items to files.
"""

from __future__ import annotations

import logging
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Protocol, TypeVar, runtime_checkable

from httpx import HTTPError

from cmem_client.exceptions import RepositoryItemNotFoundError, RepositoryReadError
from cmem_client.logging_utils import log_method
from cmem_client.models.base import Model
from cmem_client.repositories.base.abc import ItemType

if TYPE_CHECKING:
    from pathlib import Path

    from cmem_client.client import Client


class ExportConfig(Model, ABC):
    """Abstract base class for Export Item Configuration Objects"""


ExportItemConfig_contra = TypeVar("ExportItemConfig_contra", bound=ExportConfig, contravariant=True)


@runtime_checkable
class ExportItemProtocol(Protocol[ItemType, ExportItemConfig_contra]):
    """Protocol which allows for exporting of items to a file path.

    This protocol defines the interface that repositories must implement to support
    exporting items to files. It provides both a public interface method and requires
    implementation of a concrete export method.

    Attributes:
        _dict: Dictionary mapping item keys to repository items.
    """

    _client: Client
    _dict: dict[str, ItemType]
    _logger: logging.Logger

    @property
    def logger(self) -> logging.Logger:
        """Gets the client logger"""
        if not hasattr(self, "_logger"):
            self._logger = logging.getLogger(f"{self._client.logger.name}.{self.__class__.__name__}")
        return self._logger

    @log_method
    def export_item(
        self,
        key: str,
        path: Path | None = None,
        replace: bool = False,
        configuration: ExportItemConfig_contra | None = None,
    ) -> Path:
        """Export an item from the repository to a file path.

        Args:
            key: The key identifying the item to export.
            path: The target file path for export. If None, a path will be generated.
            replace: Whether to replace existing files at the target path.
            configuration: Optional configuration for export behavior.

        Returns:
            The actual path where the item was exported.

        Raises:
            RepositoryItemNotFoundError: If the specified item key is not found.
            RepositoryReadError: If there's an error during export or path mismatch.
        """
        if key not in self._dict:
            raise RepositoryItemNotFoundError(f"Repository item '{key}' not found.")
        try:
            new_path = self._export_item(path=path, key=key, replace=replace, configuration=configuration)
        except HTTPError as error:
            raise RepositoryReadError(f"Error on exporting repository item '{key}'.") from error
        if path and path != new_path:
            raise RepositoryReadError(
                f"Repository export returned different path than requested: '{path}' != `{new_path}`"
            )
        self.logger.info("Repository item '%s' exported.", key)
        if path:
            return path

        return new_path

    @abstractmethod
    def _export_item(
        self, key: str, path: Path | None, replace: bool = False, configuration: ExportItemConfig_contra | None = None
    ) -> Path:
        """Export an item from the repository (concrete implementation).

        This method must be implemented by concrete repository classes to handle
        the actual export operation. It performs the low-level work of writing
        the item data to the specified file path.

        Args:
            key: The key identifying the item to export.
            path: The file path where the item should be exported. If None, a path
                should be generated by the implementation.
            replace: Whether to replace existing files at the target path.
            configuration: Optional configuration for export behavior.

        Returns:
            The actual path where the item was exported. This may differ from the
            input path if the implementation generates a different filename.
        """
