from __future__ import annotations

from mf_core import (
    SENTINEL,
    MAX_POOL_SIZE,
    DEFAULT_TIMEOUT,
    HTTP_OK,
    HTTP_UNAVAILABLE,
    CoreError,
    RetryPolicy,
    ConnectionConfig,
)


class TransportError(CoreError):
    transport_code: int = 0

    def describe(self: "TransportError") -> str:
        base: str = CoreError.describe(self)
        return base + " transport=" + str(self.transport_code)

    def is_retryable(self: "TransportError") -> bool:
        return self.transport_code < HTTP_UNAVAILABLE


class ConnectionPool:
    config: ConnectionConfig = ConnectionConfig()
    active: int = 0
    peak: int = 0
    failed_acquires: int = 0
    total_acquired: int = 0

    def acquire(self: "ConnectionPool") -> bool:
        limit: int = MAX_POOL_SIZE
        if self.active >= limit:
            self.failed_acquires = self.failed_acquires + 1
            return False
        self.active = self.active + 1
        self.total_acquired = self.total_acquired + 1
        if self.active > self.peak:
            self.peak = self.active
        return True

    def release(self: "ConnectionPool") -> None:
        if self.active > 0:
            self.active = self.active - 1

    def utilization(self: "ConnectionPool") -> int:
        limit: int = MAX_POOL_SIZE
        if limit == 0:
            return 0
        return self.active * 100 // limit

    def is_saturated(self: "ConnectionPool") -> bool:
        return self.active >= MAX_POOL_SIZE


class Request:
    path: str = "/"
    payload_size: int = 0
    timeout: float = DEFAULT_TIMEOUT
    retry: RetryPolicy = RetryPolicy()
    attempt: int = 0

    def can_retry(self: "Request") -> bool:
        return self.retry.should_retry(self.attempt)

    def next_delay(self: "Request") -> int:
        return self.retry.delay_ms(self.attempt)

    def is_idempotent(self: "Request") -> bool:
        return self.attempt == 0


class Response:
    status: int = 0
    payload_size: int = 0
    latency_ms: int = 0
    error: TransportError = TransportError()

    def is_success(self: "Response") -> bool:
        return HTTP_OK <= self.status < 300

    def is_error(self: "Response") -> bool:
        return self.status >= 400

    def is_unavailable(self: "Response") -> bool:
        return self.status == HTTP_UNAVAILABLE


def send_request(pool: ConnectionPool, req: Request) -> Response:
    resp: Response = Response()
    if not pool.acquire():
        resp.status = HTTP_UNAVAILABLE
        resp.error.code = HTTP_UNAVAILABLE
        return resp
    resp.status = HTTP_OK
    resp.latency_ms = req.next_delay()
    pool.release()
    return resp


def send_with_retry(pool: ConnectionPool, req: Request) -> Response:
    resp: Response = Response()
    while req.can_retry():
        resp = send_request(pool, req)
        if resp.is_success():
            return resp
        req.attempt = req.attempt + 1
    sentinel_resp: Response = Response()
    sentinel_resp.status = SENTINEL
    sentinel_resp.error.code = SENTINEL
    return sentinel_resp


GLOBAL_POOL: ConnectionPool = ConnectionPool()
GLOBAL_TIMEOUT: float = DEFAULT_TIMEOUT
DEFAULT_RETRY: RetryPolicy = RetryPolicy()
