import logging
import socket
from collections.abc import Callable
from pathlib import Path
from typing import Any

import openziti
from uvicorn import config, server
from uvicorn.lifespan.on import LifespanOn
from uvicorn.protocols.http.httptools_impl import HttpToolsProtocol as UvHttpToolsProtocol

from mrok.proxy.models import Identity
from mrok.types.proxy import ASGIApp

logger = logging.getLogger("mrok.proxy")

config.LIFESPAN["auto"] = "mrok.proxy.ziticorn:Lifespan"


class Lifespan(LifespanOn):
    def __init__(self, lf_config: config.Config) -> None:
        super().__init__(lf_config)
        self.logger = logging.getLogger("mrok.proxy")


class HttpToolsProtocol(UvHttpToolsProtocol):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.logger = logging.getLogger("mrok.proxy")
        self.access_logger = logging.getLogger("mrok.access")
        self.access_log = self.access_logger.hasHandlers()


class Server(server.Server):
    async def serve(self, sockets: list[socket.socket] | None = None) -> None:
        if not sockets:
            sockets = [self.config.bind_socket()]
        with self.capture_signals():
            await self._serve(sockets)


class BackendConfig(config.Config):
    def __init__(
        self,
        app: ASGIApp | Callable[..., Any] | str,
        identity_file: str | Path,
        ziti_load_timeout_ms: int = 5000,
        backlog: int = 2048,
        timeout_keep_alive: int = 5,
        limit_concurrency: int | None = None,
        limit_max_requests: int | None = None,
    ):
        self.identity_file = identity_file
        self.identity = Identity.load_from_file(self.identity_file)
        self.ziti_load_timeout_ms = ziti_load_timeout_ms
        super().__init__(
            app,
            loop="asyncio",
            http=HttpToolsProtocol,
            backlog=backlog,
            timeout_keep_alive=timeout_keep_alive,
            limit_concurrency=limit_concurrency,
            limit_max_requests=limit_max_requests,
        )

    def bind_socket(self) -> socket.socket:
        logger.info(
            "Connect to Ziti service "
            f"'{self.identity.mrok.extension} ({self.identity.mrok.instance})'"
        )

        ctx, err = openziti.load(str(self.identity_file), timeout=self.ziti_load_timeout_ms)
        if err != 0:
            raise RuntimeError(f"Failed to load Ziti identity from {self.identity_file}: {err}")

        sock = ctx.bind(self.identity.mrok.extension)
        sock.listen(self.backlog)
        logger.info(f"listening on ziti service {self.identity.mrok.extension} for connections")
        return sock

    def configure_logging(self) -> None:
        return
