from __future__ import annotations

import contextlib
import typing
from collections.abc import Generator

from .._models import (
    URL,
    Extensions,
    HeaderTypes,
    Origin,
    Request,
    Response,
    enforce_bytes,
    enforce_headers,
    enforce_url,
    include_request_headers,
)


class RequestInterface:
    def request(
        self,
        method: bytes | str,
        url: URL | bytes | str,
        *,
        headers: HeaderTypes = None,
        content: bytes | typing.Iterator[bytes] | None = None,
        extensions: Extensions | None = None,
    ) -> Response:
        # Strict type checking on our parameters.
        method = enforce_bytes(method, name="method")
        url = enforce_url(url, name="url")
        headers = enforce_headers(headers, name="headers")

        # Include Host header, and optionally Content-Length or Transfer-Encoding.
        headers = include_request_headers(headers, url=url, content=content)

        request = Request(
            method=method,
            url=url,
            headers=headers,
            content=content,
            extensions=extensions,
        )
        response = self.handle_request(request)
        try:
            response.read()
        finally:
            response.close()
        return response

    @contextlib.contextmanager
    def stream(
        self,
        method: bytes | str,
        url: URL | bytes | str,
        *,
        headers: HeaderTypes = None,
        content: bytes | typing.Iterator[bytes] | None = None,
        extensions: Extensions | None = None,
    ) -> Generator[Response]:
        # Strict type checking on our parameters.
        method = enforce_bytes(method, name="method")
        url = enforce_url(url, name="url")
        headers = enforce_headers(headers, name="headers")

        # Include Host header, and optionally Content-Length or Transfer-Encoding.
        headers = include_request_headers(headers, url=url, content=content)

        request = Request(
            method=method,
            url=url,
            headers=headers,
            content=content,
            extensions=extensions,
        )
        response = self.handle_request(request)
        try:
            yield response
        finally:
            response.close()

    def handle_request(self, request: Request) -> Response:
        raise NotImplementedError()  # pragma: no cover


class ConnectionInterface(RequestInterface):
    def close(self) -> None:
        raise NotImplementedError()  # pragma: no cover

    def info(self) -> str:
        raise NotImplementedError()  # pragma: no cover

    def can_handle_request(self, origin: Origin) -> bool:
        raise NotImplementedError()  # pragma: no cover

    def is_connected(self) -> bool:
        """
        Return `True` if the connection is open (the underlying socket has been
        established).  A connection in the NEW state (just created but not yet
        connected) returns `False`.

        Note: for some implementations `is_connected() != not is_closed()`.
        The default implementation returns `not self.is_closed()`, which is
        correct for connections that are never in the NEW (pre-TCP) state.
        """
        return not self.is_closed()  # pragma: no cover

    def is_available(self) -> bool:
        """
        Return `True` if the connection is currently able to accept an
        outgoing request.

        An HTTP/1.1 connection will only be available if it is currently idle.

        An HTTP/2 connection will be available so long as the stream ID space is
        not yet exhausted, and the connection is not in an error state.

        While the connection is being established we may not yet know if it is going
        to result in an HTTP/1.1 or HTTP/2 connection. The connection should be
        treated as being available, but might ultimately raise `NewConnectionRequired`
        required exceptions if multiple requests are attempted over a connection
        that ends up being established as HTTP/1.1.
        """
        raise NotImplementedError()  # pragma: no cover

    def has_expired(self) -> bool:
        """
        Return `True` if the connection is in a state where it should be closed.

        This either means that the connection is idle and it has passed the
        expiry time on its keep-alive, or that server has sent an EOF.
        """
        raise NotImplementedError()  # pragma: no cover

    def is_idle(self) -> bool:
        """
        Return `True` if the connection is currently idle.
        """
        raise NotImplementedError()  # pragma: no cover

    def can_multiplex(self) -> bool:
        """
        Return `True` if the connection can serve multiple requests
        concurrently, such as an established HTTP/2 connection.

        The default covers HTTP/1.1-style implementations, which serve a
        single request at a time.
        """
        return False

    def is_closed(self) -> bool:
        """
        Return `True` if the connection has been closed.

        Used when a response is closed to determine if the connection may be
        returned to the connection pool or not.
        """
        raise NotImplementedError()  # pragma: no cover
