from __future__ import annotations

import logging
from enum import Enum, unique
from typing import TYPE_CHECKING, Any, cast

from iaqualink.device import (
    AqualinkDevice,
    AqualinkSensor,
    AqualinkSwitch,
    AqualinkThermostat,
)
from iaqualink.exception import AqualinkInvalidParameterException

if TYPE_CHECKING:
    from collections.abc import Callable, Coroutine

    from iaqualink.systems.exo.system import ExoSystem
    from iaqualink.typing import DeviceData

EXO_TEMP_CELSIUS_LOW = 1
EXO_TEMP_CELSIUS_HIGH = 40

LOGGER = logging.getLogger("iaqualink")


@unique
class ExoState(Enum):
    OFF = 0
    ON = 1


class ExoDevice(AqualinkDevice):
    def __init__(self, system: ExoSystem, data: DeviceData):
        super().__init__(system, data)

        # This silences mypy errors due to AqualinkDevice type annotations.
        self.system: ExoSystem = system

    @property
    def label(self) -> str:
        name = self.name
        return " ".join([x.capitalize() for x in name.split("_")])

    @property
    def state(self) -> str:
        return str(self.data["state"])

    @property
    def name(self) -> str:
        return self.data["name"]

    @property
    def manufacturer(self) -> str:
        return "Zodiac"

    @property
    def model(self) -> str:
        return self.__class__.__name__.replace("Exo", "")

    @classmethod
    def from_data(cls, system: ExoSystem, data: DeviceData) -> ExoDevice:
        class_: type[ExoDevice]

        if data["name"].startswith("aux_"):
            class_ = ExoAuxSwitch
        elif data["name"].startswith("sns_"):
            class_ = ExoSensor
        elif data["name"] == "heating":
            class_ = ExoThermostat
        elif data["name"] == "heater":
            class_ = ExoHeater
        elif data["name"] in ["production", "boost", "low"]:
            class_ = ExoAttributeSwitch
        elif data["name"] == "filter_pump":
            class_ = ExoFilterPump
        elif data["name"] in ["error_code", "error_state"]:
            class_ = ExoErrorSensor
        else:
            class_ = ExoAttributeSensor

        return class_(system, data)


class ExoSensor(ExoDevice, AqualinkSensor):
    """These sensors are called sns_#."""

    @property
    def is_on(self) -> bool:
        return ExoState(self.data["state"]) == ExoState.ON

    @property
    def state(self) -> str:
        if self.is_on:
            return str(self.data["value"])
        return ""

    @property
    def label(self) -> str:
        return self.data["sensor_type"]

    @property
    def name(self) -> str:
        # XXX: We're using the label as name rather than "sns_#".
        # Might revisit later.
        return self.data["sensor_type"].lower().replace(" ", "_")


class ExoAttributeSensor(ExoDevice, AqualinkSensor):
    """These sensors are a simple key/value in equipment->swc_0."""


class ExoErrorSensor(ExoAttributeSensor):
    """Sensor for error_code and error_state diagnostic fields."""


# This is an abstract class, not to be instantiated directly.
class ExoSwitch(ExoDevice, AqualinkSwitch):
    @property
    def label(self) -> str:
        return self.name.replace("_", " ").capitalize()

    @property
    def is_on(self) -> bool:
        return ExoState(self.data["state"]) == ExoState.ON

    @property
    def _command(self) -> Callable[[str, int], Coroutine[Any, Any, None]]:
        raise NotImplementedError

    async def turn_on(self) -> None:
        if not self.is_on:
            await self._command(self.name, 1)

    async def turn_off(self) -> None:
        if self.is_on:
            await self._command(self.name, 0)


class ExoAuxSwitch(ExoSwitch):
    @property
    def _command(self) -> Callable[[str, int], Coroutine[Any, Any, None]]:
        return self.system.set_aux


class ExoFilterPump(ExoSwitch):
    @property
    def _command(self) -> Callable[[str, int], Coroutine[Any, Any, None]]:
        return self.system.set_filter_pump


class ExoAttributeSwitch(ExoSwitch):
    @property
    def _command(self) -> Callable[[str, int], Coroutine[Any, Any, None]]:
        return self.system.set_toggle


class ExoHeater(ExoDevice):
    """This device is to separate the state of the heater from the thermostat to maintain the existing homeassistant API"""


class ExoThermostat(ExoSwitch, AqualinkThermostat):
    @property
    def state(self) -> str:
        return str(self.data["sp"])

    @property
    def unit(self) -> str:
        return "C"

    @property
    def _sensor(self) -> ExoSensor:
        return cast(ExoSensor, self.system.devices["sns_3"])

    @property
    def _heater(self) -> ExoHeater:
        return cast(ExoHeater, self.system.devices["heater"])

    @property
    def current_temperature(self) -> str:
        return self._sensor.state

    @property
    def target_temperature(self) -> str:
        return str(self.data["sp"])

    @property
    def min_temperature(self) -> int:
        return int(self.data["sp_min"])

    @property
    def max_temperature(self) -> int:
        return int(self.data["sp_max"])

    async def set_temperature(self, temperature: int) -> None:
        unit = self.unit
        low = self.min_temperature
        high = self.max_temperature

        if temperature not in range(low, high + 1):
            msg = f"{temperature}{unit} isn't a valid temperature"
            msg += f" ({low}-{high}{unit})."
            raise AqualinkInvalidParameterException(msg)

        await self.system.set_heating("sp", temperature)

    @property
    def is_on(self) -> bool:
        return ExoState(self.data["enabled"]) == ExoState.ON

    async def turn_on(self) -> None:
        if self.is_on is False:
            await self.system.set_heating("enabled", 1)

    async def turn_off(self) -> None:
        if self.is_on is True:
            await self.system.set_heating("enabled", 0)
