"""Provide a gateway."""

from __future__ import annotations

from collections.abc import AsyncGenerator
from dataclasses import dataclass, field
import logging
from types import TracebackType
from typing import Self

from .model.message import Message, MessageSchema
from .model.node import Node
from .model.protocol import (
    DEFAULT_PROTOCOL_VERSION,
    ProtocolType,
    get_protocol,
)
from .persistence import Persistence
from .transport import Transport

LOGGER = logging.getLogger(__package__)


class Gateway:
    """Represent a MySensors gateway."""

    def __init__(self, transport: Transport, config: Config | None = None) -> None:
        """Set up gateway."""
        self.config = config or Config()
        self.nodes: dict[int, Node] = {}
        self.persistence: Persistence | None = None
        if self.config.persistence_file:
            self.persistence = Persistence(self.nodes, self.config.persistence_file)
        self.transport = transport
        protocol = self._protocol = get_protocol(DEFAULT_PROTOCOL_VERSION)
        message_buffer = self._message_buffer = MessageBuffer()
        message_schema = self._message_schema = MessageSchema(protocol)
        self._incoming_message_handler = protocol.IncomingMessageHandler(
            self, message_buffer, message_schema
        )
        self._outgoing_message_handler = protocol.OutgoingMessageHandler(
            self, message_buffer, message_schema
        )
        self._protocol_version: str | None = None

    @property
    def protocol(self) -> ProtocolType:
        """Return the correct protocol."""
        return self._protocol

    @property
    def protocol_version(self) -> str | None:
        """Return the protocol version."""
        return self._protocol_version

    @protocol_version.setter
    def protocol_version(self, value: str) -> None:
        """Return the protocol version."""
        self._protocol_version = value
        protocol = self._protocol = get_protocol(self._protocol_version)
        message_schema = self._message_schema = MessageSchema(protocol)
        self._incoming_message_handler = protocol.IncomingMessageHandler(
            self, self._message_buffer, message_schema
        )
        self._outgoing_message_handler = protocol.OutgoingMessageHandler(
            self, self._message_buffer, message_schema
        )

    async def listen(self) -> AsyncGenerator[Message]:
        """Listen and yield a message."""
        while True:
            decoded_message = await self.transport.read()
            message = Message.from_string(decoded_message, self._message_schema)
            message_handler = self._incoming_message_handler.get_message_handler(
                self.protocol, message
            )
            message = await message_handler(message)

            yield message

    async def send(self, message: Message, *, message_buffer: bool = True) -> None:
        """Send a message."""
        LOGGER.debug("Sending: %s", message)
        if message_buffer:
            message_handler = self._outgoing_message_handler.get_message_handler(
                self.protocol, message
            )
            await message_handler(message)
            return

        decoded_message = message.to_string(self._message_schema)

        await self.transport.write(decoded_message)

    async def __aenter__(self) -> Self:
        """Connect to the transport."""
        if self.persistence:
            await self.persistence.load()
            await self.persistence.start()
        await self.transport.connect()
        return self

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc_value: BaseException | None,
        traceback: TracebackType | None,
    ) -> None:
        """Disconnect from the transport."""
        await self.transport.disconnect()
        if self.persistence:
            await self.persistence.stop()


@dataclass
class Config:
    """Represent the gateway config."""

    metric: bool = True
    persistence_file: str | None = None


@dataclass
class MessageBuffer:
    """Represent a sleep message buffer."""

    internal_messages: dict[tuple[int, int, int], Message] = field(default_factory=dict)
    set_messages: dict[tuple[int, int, int], Message] = field(default_factory=dict)
