from abc import abstractmethod
from contextlib import contextmanager
from typing import Protocol, Type, runtime_checkable, Generator, ClassVar, Optional

from pydantic import BaseModel

from requests import Request

__all__ = [
    'EventHandler',
    'SecretVault',
    'RequestAuthorizer',
    'AsyncRequestAuthorizer',
    'KeyValueRepository'
]


@runtime_checkable
class EventHandler[InputData, OutputData](Protocol):
    """Defines a protocol for event handlers.

    An `EventHandler` is designed to process input data within a context,
    yielding an output. This is typically used for operations that involve
    resource setup before processing and cleanup afterwards. Additionally, a
    protocol convention is helpful to create high-order functions and classes
    in order to reuse functionality.

    Type variables:
        InputData: The type of data expected as input to the event.
        OutputData: The type of data yielded as output from the event.

    Example::

        class FileProcessor(EventHandler[str, bytes]):
            @contextmanager
            def on_event(self, filepath: str) -> Generator[bytes, Any, Any]:
                print(f"Opening file: {filepath}")
                file_content = b""
                try:
                    with open(filepath, 'rb') as f:
                        file_content = f.read()
                    yield file_content
                finally:
                    print(f"Closing file: {filepath}")

        # Usage
        processor = FileProcessor()
        with processor.on_event("my_document.txt") as content:
            print(f"Processing content (first 10 bytes): {content[:10]}")
        # Expected output:
        # Opening file: my_document.txt
        # Processing content (first 10 bytes): b'...'
        # Closing file: my_document.txt
    """
    @abstractmethod
    @contextmanager
    def on_event(self, input_data: InputData) -> Generator[OutputData]:
        """
        An abstract context manager method for handling events.

        This method defines the core logic for an event handler,
        providing a context for processing input data and yielding output data.

        Args:
            input_data: The input data to be processed by the event handler.

        Yields:
            OutputData: The output data generated by the event handler.
        """
        ...


@runtime_checkable
class SecretVault(Protocol):
    """
    Interface definition for vault-like component that safely retrieves secret
    data from a remote store.
    """

    @abstractmethod
    def get_secret[SecretModel: BaseModel](
            self,
            secret_model: Type[SecretModel],
            secret_key: str
    ) -> SecretModel:
        """
        Returns the secret specified by its `secret_key` represented as a `SecretModel`
        object.

        Args:
            secret_model: Type representation of the secret content
            secret_key: Secret unique identifier

        Returns:
            Secret data represented as the specified `SecretModel`
        """
        ...

    def get_secret_string(self, secret_name) -> str:
        raise NotImplementedError()


@runtime_checkable
class RequestAuthorizer(Protocol):
    @abstractmethod
    def authorize(self, request: Request) -> Request:
        ...


@runtime_checkable
class AsyncRequestAuthorizer(Protocol):
    @abstractmethod
    async def authorize(self, request: Request) -> Request:
        ...


class KeyValueRepository(Protocol):
    ValueType: ClassVar = int | str | float | bool

    @abstractmethod
    def get_by_name[V: KeyValueRepository.ValueType](
            self, key_type: V, key: str
    ) -> Optional[V]:
        ...

    @abstractmethod
    def set_by_name[V: KeyValueRepository.ValueType](
            self, key: str, value: V
    ) -> Optional[V]:
        ...
