from __future__ import annotations

import asyncio
import contextlib
import signal
from typing import Protocol

from gateway_core.application.ports.NodesPort import NodesPort
from gateway_core.application.services.Nodes import Nodes
from gateway_integration_deltagrid.deltaGridIntegrationFactory import buildDeltaGridIntegration
from gateway_runtime.RuntimeConfig import IntegrationType, RuntimeConfig, IntegrationConfig


class Integration(Protocol):
    async def start(self) -> None: ...
    async def stop(self) -> None: ...


class IntegrationFactory(Protocol):
    async def __call__(
        self,
        *,
        integrationConfig: IntegrationConfig,
        nodes: NodesPort,
    ) -> Integration:
        ...


class RuntimeComposer:
    _integrationRegistry: dict[IntegrationType, IntegrationFactory] = {
        "deltagrid": buildDeltaGridIntegration,
    }

    def __init__(self, *, config: RuntimeConfig) -> None:
        self._config = config
        self._nodes: NodesPort | None = None
        self._integrations: list[Integration] = []
        self._shutdownEvent = asyncio.Event()

    @property
    def nodes(self) -> NodesPort:
        if self._nodes is None:
            self._nodes = Nodes(self._config.nodeInfos)
        return self._nodes

    async def _start(self, cfg: IntegrationConfig) -> None:
        try:
            factory = self._integrationRegistry[cfg.type]
        except KeyError as e:
            allowed = ", ".join(self._integrationRegistry.keys())
            raise RuntimeError(
                f"Unsupported integration type: {cfg.type}. Allowed: {allowed}"
            ) from e

        integration = await factory(integrationConfig=cfg, nodes=self.nodes)
        self._integrations.append(integration)
        await integration.start()

    async def stop(self) -> None:
        for integration in self._integrations:
            with contextlib.suppress(Exception):
                await integration.stop()

    async def run(self) -> None:
        loop = asyncio.get_running_loop()

        def requestShutdown() -> None:
            self._shutdownEvent.set()

        # systemd SIGTERM + Ctrl+C
        with contextlib.suppress(NotImplementedError):
            loop.add_signal_handler(signal.SIGTERM, requestShutdown)
            loop.add_signal_handler(signal.SIGINT, requestShutdown)

        await asyncio.gather(*(self._start(cfg) for cfg in self._config.integrationConfigs))

        await self._shutdownEvent.wait()
        await self.stop()
