"""Cover platform for Teslemetry integration."""

from itertools import chain
from typing import Any, override

from tesla_fleet_api import firmware_at_least
from tesla_fleet_api.const import (
    ClosureState,
    Scope,
    SunRoofCommand,
    Trunk,
    WindowCommand,
)
from tesla_fleet_api.teslemetry import Vehicle
from teslemetry_stream import Signal
from teslemetry_stream.const import WindowState

from homeassistant.components.cover import (
    CoverDeviceClass,
    CoverEntity,
    CoverEntityFeature,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.restore_state import RestoreEntity

from . import TeslemetryConfigEntry
from .entity import (
    TeslemetryRootEntity,
    TeslemetryVehiclePollingEntity,
    TeslemetryVehicleStreamEntity,
)
from .helpers import handle_vehicle_command
from .models import TeslemetryVehicleData

OPEN = 1
CLOSED = 0
TONNEAU_CLOSED = "Closed"

PARALLEL_UPDATES = 0


async def async_setup_entry(
    hass: HomeAssistant,
    entry: TeslemetryConfigEntry,
    async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
    """Set up the Teslemetry cover platform from a config entry."""

    async_add_entities(
        chain(
            (
                TeslemetryVehiclePollingWindowEntity(vehicle, entry.runtime_data.scopes)
                if vehicle.poll or not firmware_at_least(vehicle.firmware, "2024.26")
                else TeslemetryStreamingWindowEntity(vehicle, entry.runtime_data.scopes)
                for vehicle in entry.runtime_data.vehicles
            ),
            (
                TeslemetryVehiclePollingChargePortEntity(
                    vehicle, entry.runtime_data.scopes
                )
                if vehicle.poll or not firmware_at_least(vehicle.firmware, "2024.44.25")
                else TeslemetryStreamingChargePortEntity(
                    vehicle, entry.runtime_data.scopes
                )
                for vehicle in entry.runtime_data.vehicles
            ),
            (
                TeslemetryVehiclePollingFrontTrunkEntity(
                    vehicle, entry.runtime_data.scopes
                )
                if vehicle.poll or not firmware_at_least(vehicle.firmware, "2024.26")
                else TeslemetryStreamingFrontTrunkEntity(
                    vehicle, entry.runtime_data.scopes
                )
                for vehicle in entry.runtime_data.vehicles
            ),
            (
                TeslemetryVehiclePollingRearTrunkEntity(
                    vehicle, entry.runtime_data.scopes
                )
                if vehicle.poll or not firmware_at_least(vehicle.firmware, "2024.26")
                else TeslemetryStreamingRearTrunkEntity(
                    vehicle, entry.runtime_data.scopes
                )
                for vehicle in entry.runtime_data.vehicles
            ),
            (
                TeslemetrySunroofEntity(vehicle, entry.runtime_data.scopes)
                for vehicle in entry.runtime_data.vehicles
                if vehicle.poll
                and vehicle.coordinator.data.get("vehicle_config_sun_roof_installed")
            ),
            (
                TeslemetryStreamingTonneauEntity(vehicle, entry.runtime_data.scopes)
                for vehicle in entry.runtime_data.vehicles
                if not vehicle.poll
                and firmware_at_least(vehicle.firmware, "2024.44.25")
                and vehicle.coordinator.data.get("vehicle_config_car_type")
                == "cybertruck"
            ),
        )
    )


class CoverRestoreEntity(RestoreEntity, CoverEntity):
    """Restore class for cover entities."""

    @override
    async def async_added_to_hass(self) -> None:
        """Handle entity which will be added."""
        await super().async_added_to_hass()
        if (state := await self.async_get_last_state()) is not None:
            if state.state == "open":
                self._attr_is_closed = False
            elif state.state == "closed":
                self._attr_is_closed = True


class TeslemetryWindowEntity(TeslemetryRootEntity, CoverEntity):
    """Base class for window cover entities."""

    api: Vehicle
    _attr_device_class = CoverDeviceClass.WINDOW
    _attr_supported_features = CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE

    @override
    async def async_open_cover(self, **kwargs: Any) -> None:
        """Vent windows."""
        self.raise_for_scope(Scope.VEHICLE_CMDS)

        await handle_vehicle_command(
            self.api.window_control(command=WindowCommand.VENT)
        )
        self._attr_is_closed = False
        self.async_write_ha_state()

    @override
    async def async_close_cover(self, **kwargs: Any) -> None:
        """Close windows."""
        self.raise_for_scope(Scope.VEHICLE_CMDS)

        await handle_vehicle_command(
            self.api.window_control(command=WindowCommand.CLOSE)
        )
        self._attr_is_closed = True
        self.async_write_ha_state()


class TeslemetryVehiclePollingWindowEntity(
    TeslemetryVehiclePollingEntity, TeslemetryWindowEntity, CoverEntity
):
    """Polling cover entity for windows."""

    def __init__(self, data: TeslemetryVehicleData, scopes: list[Scope]) -> None:
        """Initialize the cover."""
        super().__init__(data, "windows")
        self.scoped = Scope.VEHICLE_CMDS in scopes
        if not self.scoped:
            self._attr_supported_features = CoverEntityFeature(0)

    @override
    def _async_update_attrs(self) -> None:
        """Update the entity attributes."""
        fd = self.get("vehicle_state_fd_window")
        fp = self.get("vehicle_state_fp_window")
        rd = self.get("vehicle_state_rd_window")
        rp = self.get("vehicle_state_rp_window")

        if OPEN in (fd, fp, rd, rp):
            self._attr_is_closed = False
        elif None in (fd, fp, rd, rp):
            self._attr_is_closed = None
        else:
            self._attr_is_closed = True


class TeslemetryStreamingWindowEntity(
    TeslemetryVehicleStreamEntity, TeslemetryWindowEntity, CoverRestoreEntity
):
    """Streaming cover entity for windows."""

    fd: bool | None = None
    fp: bool | None = None
    rd: bool | None = None
    rp: bool | None = None

    def __init__(self, data: TeslemetryVehicleData, scopes: list[Scope]) -> None:
        """Initialize the cover."""
        super().__init__(
            data,
            "windows",
        )
        self.scoped = Scope.VEHICLE_CMDS in scopes
        if not self.scoped:
            self._attr_supported_features = CoverEntityFeature(0)
        self._attr_is_closed = None

    @override
    async def async_added_to_hass(self) -> None:
        """When entity is added to hass."""
        await super().async_added_to_hass()
        self.async_on_remove(
            self.stream.async_add_listener(
                self._handle_stream_update,
                {"vin": self.vin, "data": None},
            )
        )
        for signal in (
            Signal.FD_WINDOW,
            Signal.FP_WINDOW,
            Signal.RD_WINDOW,
            Signal.RP_WINDOW,
        ):
            self.vehicle.config_entry.async_create_background_task(
                self.hass,
                self.add_field(signal),
                f"Adding field {signal} to {self.vehicle.vin}",
            )

    def _handle_stream_update(self, data: dict[str, Any]) -> None:
        """Update the entity attributes."""

        change = False
        if value := data["data"].get(Signal.FD_WINDOW):
            self.fd = WindowState.get(value) == "Closed"
            change = True
        if value := data["data"].get(Signal.FP_WINDOW):
            self.fp = WindowState.get(value) == "Closed"
            change = True
        if value := data["data"].get(Signal.RD_WINDOW):
            self.rd = WindowState.get(value) == "Closed"
            change = True
        if value := data["data"].get(Signal.RP_WINDOW):
            self.rp = WindowState.get(value) == "Closed"
            change = True

        if not change:
            return

        if False in (self.fd, self.fp, self.rd, self.rp):
            self._attr_is_closed = False
        elif None in (self.fd, self.fp, self.rd, self.rp):
            self._attr_is_closed = None
        else:
            self._attr_is_closed = True

        self.async_write_ha_state()


class TeslemetryChargePortEntity(
    TeslemetryRootEntity,
    CoverEntity,
):
    """Base class for for charge port cover entities."""

    api: Vehicle
    _attr_device_class = CoverDeviceClass.DOOR
    _attr_supported_features = CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE

    @override
    async def async_open_cover(self, **kwargs: Any) -> None:
        """Open charge port."""
        self.raise_for_scope(Scope.VEHICLE_CHARGING_CMDS)

        await handle_vehicle_command(self.api.charge_port_door_open())
        self._attr_is_closed = False
        self.async_write_ha_state()

    @override
    async def async_close_cover(self, **kwargs: Any) -> None:
        """Close charge port."""
        self.raise_for_scope(Scope.VEHICLE_CHARGING_CMDS)

        await handle_vehicle_command(self.api.charge_port_door_close())
        self._attr_is_closed = True
        self.async_write_ha_state()


class TeslemetryVehiclePollingChargePortEntity(
    TeslemetryVehiclePollingEntity, TeslemetryChargePortEntity
):
    """Polling cover entity for the charge port."""

    def __init__(self, vehicle: TeslemetryVehicleData, scopes: list[Scope]) -> None:
        """Initialize the cover."""
        super().__init__(vehicle, "charge_state_charge_port_door_open")
        self.scoped = any(
            scope in scopes
            for scope in (Scope.VEHICLE_CMDS, Scope.VEHICLE_CHARGING_CMDS)
        )
        self._attr_supported_features = (
            CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE
        )
        if not self.scoped:
            self._attr_supported_features = CoverEntityFeature(0)

    @override
    def _async_update_attrs(self) -> None:
        """Update the entity attributes."""
        value = self._value
        self._attr_is_closed = None if value is None else not value


class TeslemetryStreamingChargePortEntity(
    TeslemetryVehicleStreamEntity, TeslemetryChargePortEntity, CoverRestoreEntity
):
    """Streaming cover entity for the charge port."""

    def __init__(self, vehicle: TeslemetryVehicleData, scopes: list[Scope]) -> None:
        """Initialize the sensor."""
        super().__init__(
            vehicle,
            "charge_state_charge_port_door_open",
        )
        self.scoped = any(
            scope in scopes
            for scope in (Scope.VEHICLE_CMDS, Scope.VEHICLE_CHARGING_CMDS)
        )
        if not self.scoped:
            self._attr_supported_features = CoverEntityFeature(0)
        self._attr_is_closed = None

    @override
    async def async_added_to_hass(self) -> None:
        """When entity is added to hass."""
        await super().async_added_to_hass()
        self.async_on_remove(
            self.vehicle.stream_vehicle.listen_ChargePortDoorOpen(
                self._async_value_from_stream
            )
        )

    def _async_value_from_stream(self, value: bool | None) -> None:
        """Update the value of the entity."""
        self._attr_is_closed = None if value is None else not value
        self.async_write_ha_state()


class TeslemetryFrontTrunkEntity(TeslemetryRootEntity, CoverEntity):
    """Base class for the front trunk cover entities."""

    api: Vehicle
    _attr_device_class = CoverDeviceClass.DOOR
    _attr_supported_features = CoverEntityFeature.OPEN

    @override
    async def async_open_cover(self, **kwargs: Any) -> None:
        """Open front trunk."""
        self.raise_for_scope(Scope.VEHICLE_CMDS)

        await handle_vehicle_command(self.api.actuate_trunk(Trunk.FRONT))
        self._attr_is_closed = False
        self.async_write_ha_state()

    # In the future this could be extended to add
    # aftermarket close support through an option flow


class TeslemetryVehiclePollingFrontTrunkEntity(
    TeslemetryVehiclePollingEntity, TeslemetryFrontTrunkEntity
):
    """Polling cover entity for the front trunk."""

    def __init__(self, vehicle: TeslemetryVehicleData, scopes: list[Scope]) -> None:
        """Initialize the cover."""
        self.scoped = Scope.VEHICLE_CMDS in scopes
        if not self.scoped:
            self._attr_supported_features = CoverEntityFeature(0)
        super().__init__(vehicle, "vehicle_state_ft")

    @override
    def _async_update_attrs(self) -> None:
        """Update the entity attributes."""
        value = self._value
        self._attr_is_closed = None if value is None else value == CLOSED


class TeslemetryStreamingFrontTrunkEntity(
    TeslemetryVehicleStreamEntity, TeslemetryFrontTrunkEntity, CoverRestoreEntity
):
    """Streaming cover entity for the front trunk."""

    def __init__(self, vehicle: TeslemetryVehicleData, scopes: list[Scope]) -> None:
        """Initialize the sensor."""
        super().__init__(vehicle, "vehicle_state_ft")
        self.scoped = Scope.VEHICLE_CMDS in scopes
        if not self.scoped:
            self._attr_supported_features = CoverEntityFeature(0)
        self._attr_is_closed = None

    @override
    async def async_added_to_hass(self) -> None:
        """When entity is added to hass."""
        await super().async_added_to_hass()
        self.async_on_remove(
            self.vehicle.stream_vehicle.listen_TrunkFront(self._async_value_from_stream)
        )

    def _async_value_from_stream(self, value: bool | None) -> None:
        """Update the entity attributes."""

        self._attr_is_closed = None if value is None else not value
        self.async_write_ha_state()


class TeslemetryRearTrunkEntity(TeslemetryRootEntity, CoverEntity):
    """Cover entity for the rear trunk."""

    api: Vehicle
    _attr_device_class = CoverDeviceClass.DOOR
    _attr_supported_features = CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE

    @override
    async def async_open_cover(self, **kwargs: Any) -> None:
        """Open rear trunk."""
        if self.is_closed is not False:
            self.raise_for_scope(Scope.VEHICLE_CMDS)

            await handle_vehicle_command(self.api.actuate_trunk(Trunk.REAR))
            self._attr_is_closed = False
            self.async_write_ha_state()

    @override
    async def async_close_cover(self, **kwargs: Any) -> None:
        """Close rear trunk."""
        if self.is_closed is not True:
            self.raise_for_scope(Scope.VEHICLE_CMDS)

            await handle_vehicle_command(self.api.actuate_trunk(Trunk.REAR))
            self._attr_is_closed = True
            self.async_write_ha_state()


class TeslemetryVehiclePollingRearTrunkEntity(
    TeslemetryVehiclePollingEntity, TeslemetryRearTrunkEntity
):
    """Base class for the rear trunk cover entities."""

    def __init__(self, vehicle: TeslemetryVehicleData, scopes: list[Scope]) -> None:
        """Initialize the sensor."""
        self.scoped = Scope.VEHICLE_CMDS in scopes
        if not self.scoped:
            self._attr_supported_features = CoverEntityFeature(0)
        super().__init__(vehicle, "vehicle_state_rt")

    @override
    def _async_update_attrs(self) -> None:
        """Update the entity attributes."""
        value = self._value
        self._attr_is_closed = None if value is None else value == CLOSED


class TeslemetryStreamingRearTrunkEntity(
    TeslemetryVehicleStreamEntity, TeslemetryRearTrunkEntity, CoverRestoreEntity
):
    """Polling cover entity for the rear trunk."""

    def __init__(self, vehicle: TeslemetryVehicleData, scopes: list[Scope]) -> None:
        """Initialize the cover."""
        super().__init__(vehicle, "vehicle_state_rt")
        self.scoped = Scope.VEHICLE_CMDS in scopes
        if not self.scoped:
            self._attr_supported_features = CoverEntityFeature(0)
        self._attr_is_closed = None

    @override
    async def async_added_to_hass(self) -> None:
        """When entity is added to hass."""
        await super().async_added_to_hass()
        self.async_on_remove(
            self.vehicle.stream_vehicle.listen_TrunkRear(self._async_value_from_stream)
        )

    def _async_value_from_stream(self, value: bool | None) -> None:
        """Update the entity attributes."""

        self._attr_is_closed = None if value is None else not value
        self.async_write_ha_state()


class TeslemetrySunroofEntity(TeslemetryVehiclePollingEntity, CoverEntity):
    """Cover entity for the sunroof."""

    api: Vehicle
    _attr_device_class = CoverDeviceClass.WINDOW
    _attr_supported_features = (
        CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE | CoverEntityFeature.STOP
    )
    _attr_entity_registry_enabled_default = False

    def __init__(self, vehicle: TeslemetryVehicleData, scopes: list[Scope]) -> None:
        """Initialize the cover."""
        super().__init__(vehicle, "vehicle_state_sun_roof_state")

        self.scoped = Scope.VEHICLE_CMDS in scopes
        if not self.scoped:
            self._attr_supported_features = CoverEntityFeature(0)

    @override
    def _async_update_attrs(self) -> None:
        """Update the entity attributes."""
        value = self._value
        if value in (None, "unknown"):
            self._attr_is_closed = None
        else:
            self._attr_is_closed = value == "closed"

        self._attr_current_cover_position = self.get(
            "vehicle_state_sun_roof_percent_open"
        )

    @override
    async def async_open_cover(self, **kwargs: Any) -> None:
        """Open sunroof."""
        self.raise_for_scope(Scope.VEHICLE_CMDS)
        await handle_vehicle_command(self.api.sun_roof_control(SunRoofCommand.VENT))
        self._attr_is_closed = False
        self.async_write_ha_state()

    @override
    async def async_close_cover(self, **kwargs: Any) -> None:
        """Close sunroof."""
        self.raise_for_scope(Scope.VEHICLE_CMDS)
        await handle_vehicle_command(self.api.sun_roof_control(SunRoofCommand.CLOSE))
        self._attr_is_closed = True
        self.async_write_ha_state()

    @override
    async def async_stop_cover(self, **kwargs: Any) -> None:
        """Close sunroof."""
        self.raise_for_scope(Scope.VEHICLE_CMDS)
        await handle_vehicle_command(self.api.sun_roof_control(SunRoofCommand.STOP))
        self._attr_is_closed = False
        self.async_write_ha_state()


class TeslemetryTonneauEntity(TeslemetryRootEntity, CoverEntity):
    """Base class for the Cybertruck tonneau cover entity."""

    api: Vehicle
    _attr_device_class = CoverDeviceClass.DOOR
    _attr_supported_features = (
        CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE | CoverEntityFeature.STOP
    )

    @override
    async def async_open_cover(self, **kwargs: Any) -> None:
        """Open tonneau."""
        self.raise_for_scope(Scope.VEHICLE_CMDS)
        await handle_vehicle_command(self.api.closure(tonneau=ClosureState.OPEN))
        self._attr_is_closed = False
        self.async_write_ha_state()

    @override
    async def async_close_cover(self, **kwargs: Any) -> None:
        """Close tonneau."""
        self.raise_for_scope(Scope.VEHICLE_CMDS)
        await handle_vehicle_command(self.api.closure(tonneau=ClosureState.CLOSE))
        self._attr_is_closed = True
        self.async_write_ha_state()

    @override
    async def async_stop_cover(self, **kwargs: Any) -> None:
        """Stop tonneau."""
        self.raise_for_scope(Scope.VEHICLE_CMDS)
        await handle_vehicle_command(self.api.closure(tonneau=ClosureState.STOP))
        self._attr_is_closed = False
        self.async_write_ha_state()


class TeslemetryStreamingTonneauEntity(
    TeslemetryVehicleStreamEntity, TeslemetryTonneauEntity, CoverRestoreEntity
):
    """Streaming cover entity for the Cybertruck tonneau."""

    def __init__(self, vehicle: TeslemetryVehicleData, scopes: list[Scope]) -> None:
        """Initialize the cover."""
        super().__init__(vehicle, "tonneau")
        # Kept as an attribute rather than checked upfront, matching how scoping
        # is handled for the other entities in this integration.
        self.scoped = Scope.VEHICLE_CMDS in scopes
        if not self.scoped:
            self._attr_supported_features = CoverEntityFeature(0)
        self._attr_is_closed = None

    @override
    async def async_added_to_hass(self) -> None:
        """When entity is added to hass."""
        await super().async_added_to_hass()
        self.async_on_remove(
            self.vehicle.stream_vehicle.listen_TonneauPosition(
                self._async_position_from_stream
            )
        )
        self.async_on_remove(
            self.vehicle.stream_vehicle.listen_TonneauOpenPercent(
                self._async_percent_from_stream
            )
        )

    def _async_position_from_stream(self, value: str | None) -> None:
        """Update the entity attributes."""
        # None and "Invalid" are treated the same as "Unknown" here as
        # defensive handling: a past bug interpreted None as false and
        # "Invalid" as true for a similar streaming field.
        if value in (None, "Unknown", "Invalid"):
            self._attr_is_closed = None
        else:
            self._attr_is_closed = value == TONNEAU_CLOSED
        self.async_write_ha_state()

    def _async_percent_from_stream(self, value: float | None) -> None:
        """Update the entity attributes."""
        self._attr_current_cover_position = None if value is None else int(value)
        self.async_write_ha_state()
