from __future__ import annotations

__all__ = ["Headers", "RequestContext"]


import time
from typing import TYPE_CHECKING, Generic, TypeVar

from ._headers import Headers

if TYPE_CHECKING:
    from .method import MethodInfo

REQ = TypeVar("REQ")
RES = TypeVar("RES")


class RequestContext(Generic[REQ, RES]):
    """Additional context for an RPC request message."""

    _method: MethodInfo[REQ, RES]
    _http_method: str
    _request_headers: Headers
    _response_headers: Headers | None
    _response_trailers: Headers | None

    def __init__(
        self,
        *,
        method: MethodInfo[REQ, RES],
        http_method: str,
        request_headers: Headers,
        timeout_ms: int | None = None,
        server_address: str | None = None,
        client_address: str | None = None,
    ) -> None:
        """
        Initialize a Context object.
        """
        self._method = method
        self._http_method = http_method
        self._request_headers = request_headers
        self._response_headers = None
        self._response_trailers = None
        self._server_address = server_address
        self._client_address = client_address

        if timeout_ms is None:
            self._end_time = None
        else:
            self._end_time = time.monotonic() + (timeout_ms / 1000.0)

    @property
    def method(self) -> MethodInfo[REQ, RES]:
        """Returns information about the RPC method being invoked."""
        return self._method

    @property
    def http_method(self) -> str:
        """Returns the HTTP method for this request.

        This is nearly always POST, but side-effect-free unary RPCs could be made
        via GET.
        """
        return self._http_method

    @property
    def request_headers(self) -> Headers:
        """Returns the request headers associated with the context."""
        return self._request_headers

    @property
    def response_headers(self) -> Headers:
        """
        Returns the response headers that will be sent before the response.
        """
        if self._response_headers is None:
            self._response_headers = Headers()
        return self._response_headers

    @property
    def response_trailers(self) -> Headers:
        """
        Returns the response trailers that will be sent after the response.
        """
        if self._response_trailers is None:
            self._response_trailers = Headers()
        return self._response_trailers

    @property
    def timeout_ms(self) -> float | None:
        """Returns the remaining time until the timeout in milliseconds, or None if no timeout is set."""
        if self._end_time is None:
            return None
        return (self._end_time - time.monotonic()) * 1000.0

    @property
    def server_address(self) -> str | None:
        """
        Returns the server address for this request, if available, as a "address:port" string.

        - On the client, this is components from the URL configured when constructing the client.
        - On the server, this is determined from the Host header and scheme of the request.
        """
        return self._server_address

    @property
    def client_address(self) -> str | None:
        """
        Returns the client address for this request, if available, as a "address:port" string.

        - On the client, this is never populated.
        - On the server, this is the value provided by the server implementation, generally
          the IP address and ephemeral port of the client.
        """
        return self._client_address
