"""Base class for Wyoming providers."""

from __future__ import annotations

import asyncio

from wyoming.client import AsyncTcpClient
from wyoming.info import Describe, Info

from homeassistant.const import Platform

from .error import WyomingError

_INFO_TIMEOUT = 1
_INFO_RETRY_WAIT = 2
_INFO_RETRIES = 3


class WyomingService:
    """Hold info for Wyoming service."""

    def __init__(self, host: str, port: int, info: Info) -> None:
        """Initialize Wyoming service."""
        self.host = host
        self.port = port
        self.info = info
        self.platforms = []

        if (self.info.satellite is not None) and self.info.satellite.installed:
            # Don't load platforms for satellite services, such as local wake
            # word detection.
            return

        if any(asr.installed for asr in info.asr):
            self.platforms.append(Platform.STT)
        if any(tts.installed for tts in info.tts):
            self.platforms.append(Platform.TTS)
        if any(wake.installed for wake in info.wake):
            self.platforms.append(Platform.WAKE_WORD)

    def has_services(self) -> bool:
        """Return True if services are installed that Home Assistant can use."""
        return (
            any(asr for asr in self.info.asr if asr.installed)
            or any(tts for tts in self.info.tts if tts.installed)
            or any(wake for wake in self.info.wake if wake.installed)
            or ((self.info.satellite is not None) and self.info.satellite.installed)
        )

    def get_name(self) -> str | None:
        """Return name of first installed usable service."""

        # Wyoming satellite
        # Must be checked first because satellites may contain wake services, etc.
        if (self.info.satellite is not None) and self.info.satellite.installed:
            return self.info.satellite.name

        # ASR = automated speech recognition (speech-to-text)
        asr_installed = [asr for asr in self.info.asr if asr.installed]
        if asr_installed:
            return asr_installed[0].name

        # TTS = text-to-speech
        tts_installed = [tts for tts in self.info.tts if tts.installed]
        if tts_installed:
            return tts_installed[0].name

        # wake-word-detection
        wake_installed = [wake for wake in self.info.wake if wake.installed]
        if wake_installed:
            return wake_installed[0].name

        return None

    @classmethod
    async def create(cls, host: str, port: int) -> WyomingService | None:
        """Create a Wyoming service."""
        info = await load_wyoming_info(host, port)
        if info is None:
            return None

        return cls(host, port, info)


async def load_wyoming_info(
    host: str,
    port: int,
    retries: int = _INFO_RETRIES,
    retry_wait: float = _INFO_RETRY_WAIT,
    timeout: float = _INFO_TIMEOUT,
) -> Info | None:
    """Load info from Wyoming server."""
    wyoming_info: Info | None = None

    for _ in range(retries + 1):
        try:
            async with AsyncTcpClient(host, port) as client, asyncio.timeout(timeout):
                # Describe -> Info
                await client.write_event(Describe().event())
                while True:
                    event = await client.read_event()
                    if event is None:
                        raise WyomingError(
                            "Connection closed unexpectedly",
                        )

                    if Info.is_type(event.type):
                        wyoming_info = Info.from_event(event)
                        break  # while

                if wyoming_info is not None:
                    break  # for
        except (TimeoutError, OSError, WyomingError):
            # Sleep and try again
            await asyncio.sleep(retry_wait)

    return wyoming_info