import logging
from asyncio import AbstractEventLoop
from typing import Optional

from zino.api.legacy import Zino1ServerProtocol
from zino.api.notify import Zino1NotificationProtocol
from zino.config.models import Configuration, PollDevice
from zino.state import ZinoState
from zino.statemodels import Event

_logger = logging.getLogger(__name__)


class ZinoServer:
    """Represents the two asyncio servers that work in tandem to implement the Zino 1 legacy API:

    Port 8001 is the text-based command interface.
    Port 8002 is the text-based notification interface.
    """

    API_PORT = 8001
    NOTIFY_PORT = 8002

    def __init__(
        self,
        loop: AbstractEventLoop,
        state: ZinoState,
        polldevs: dict[str, PollDevice],
        config: Optional[Configuration] = None,
    ):
        self._loop = loop
        self.state: ZinoState = state
        self.polldevs: dict[str, PollDevice] = polldevs
        self.active_clients: set[Zino1ServerProtocol] = set()
        self.notification_channels: dict[str, Zino1NotificationProtocol] = {}
        self.notify_server = self.api_server = None
        if not config:
            # Fetch the global config (however nasty that is)
            import zino.state

            self.config = zino.state.config
        else:
            self.config = config

    def serve(self, address: str = "0.0.0.0"):
        """Sets up the two asyncio servers to serve in tandem 'forever'"""
        api_coroutine = self._loop.create_server(
            lambda: Zino1ServerProtocol(server=self, state=self.state, config=self.config, polldevs=self.polldevs),
            address,
            self.API_PORT,
        )
        self.api_server = self._loop.run_until_complete(api_coroutine)
        _logger.info("Serving API on %r", self.api_server.sockets[0].getsockname())

        notify_coroutine = self._loop.create_server(
            lambda: Zino1NotificationProtocol(server=self, state=self.state), address, self.NOTIFY_PORT
        )
        self.notify_server = self._loop.run_until_complete(notify_coroutine)
        _logger.info("Serving notifications on %r", self.notify_server.sockets[0].getsockname())

        self.state.events.add_event_observer(self.on_event_commit)

    def on_event_commit(self, new_event: Event, old_event: Optional[Event] = None) -> None:
        """Event observer to build notifications for notification channels"""
        Zino1NotificationProtocol.build_and_send_notifications(self, new_event, old_event)
