"""Matter lock."""
from __future__ import annotations

from enum import IntFlag
from typing import Any

from chip.clusters import Objects as clusters

from homeassistant.components.lock import LockEntity, LockEntityDescription
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_CODE, Platform
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from .const import LOGGER
from .entity import MatterEntity
from .helpers import get_matter
from .models import MatterDiscoverySchema


async def async_setup_entry(
    hass: HomeAssistant,
    config_entry: ConfigEntry,
    async_add_entities: AddEntitiesCallback,
) -> None:
    """Set up Matter lock from Config Entry."""
    matter = get_matter(hass)
    matter.register_platform_handler(Platform.LOCK, async_add_entities)


class MatterLock(MatterEntity, LockEntity):
    """Representation of a Matter lock."""

    features: int | None = None

    @property
    def code_format(self) -> str | None:
        """Regex for code format or None if no code is required."""
        if self.get_matter_attribute_value(
            clusters.DoorLock.Attributes.RequirePINforRemoteOperation
        ):
            min_pincode_length = int(
                self.get_matter_attribute_value(
                    clusters.DoorLock.Attributes.MinPINCodeLength
                )
            )
            max_pincode_length = int(
                self.get_matter_attribute_value(
                    clusters.DoorLock.Attributes.MaxPINCodeLength
                )
            )
            return f"^\\d{{{min_pincode_length},{max_pincode_length}}}$"

        return None

    @property
    def supports_door_position_sensor(self) -> bool:
        """Return True if the lock supports door position sensor."""
        if self.features is None:
            return False

        return bool(self.features & DoorLockFeature.kDoorPositionSensor)

    async def send_device_command(
        self,
        command: clusters.ClusterCommand,
        timed_request_timeout_ms: int = 1000,
    ) -> None:
        """Send a command to the device."""
        await self.matter_client.send_device_command(
            node_id=self._endpoint.node.node_id,
            endpoint_id=self._endpoint.endpoint_id,
            command=command,
            timed_request_timeout_ms=timed_request_timeout_ms,
        )

    async def async_lock(self, **kwargs: Any) -> None:
        """Lock the lock with pin if needed."""
        code: str = kwargs.get(
            ATTR_CODE,
            self._lock_option_default_code,
        )
        code_bytes = code.encode() if code else None
        await self.send_device_command(
            command=clusters.DoorLock.Commands.LockDoor(code_bytes)
        )

    async def async_unlock(self, **kwargs: Any) -> None:
        """Unlock the lock with pin if needed."""
        code: str = kwargs.get(
            ATTR_CODE,
            self._lock_option_default_code,
        )
        code_bytes = code.encode() if code else None
        await self.send_device_command(
            command=clusters.DoorLock.Commands.UnlockDoor(code_bytes)
        )

    @callback
    def _update_from_device(self) -> None:
        """Update the entity from the device."""

        if self.features is None:
            self.features = int(
                self.get_matter_attribute_value(clusters.DoorLock.Attributes.FeatureMap)
            )

        lock_state = self.get_matter_attribute_value(
            clusters.DoorLock.Attributes.LockState
        )

        LOGGER.debug("Lock state: %s for %s", lock_state, self.entity_id)

        if lock_state is clusters.DoorLock.Enums.DlLockState.kLocked:
            self._attr_is_locked = True
            self._attr_is_locking = False
            self._attr_is_unlocking = False
        elif lock_state is clusters.DoorLock.Enums.DlLockState.kUnlocked:
            self._attr_is_locked = False
            self._attr_is_locking = False
            self._attr_is_unlocking = False
        elif lock_state is clusters.DoorLock.Enums.DlLockState.kNotFullyLocked:
            if self.is_locked is True:
                self._attr_is_unlocking = True
            elif self.is_locked is False:
                self._attr_is_locking = True
        else:
            # According to the matter docs a null state can happen during device startup.
            self._attr_is_locked = None
            self._attr_is_locking = None
            self._attr_is_unlocking = None

        if self.supports_door_position_sensor:
            door_state = self.get_matter_attribute_value(
                clusters.DoorLock.Attributes.DoorState
            )

            assert door_state is not None

            LOGGER.debug("Door state: %s for %s", door_state, self.entity_id)

            self._attr_is_jammed = (
                door_state is clusters.DoorLock.Enums.DoorStateEnum.kDoorJammed
            )


class DoorLockFeature(IntFlag):
    """Temp enum that represents the features of a door lock.

    Should be replaced by the library provided one once that is released.
    """

    kPinCredential = 0x1  # noqa: N815
    kRfidCredential = 0x2  # noqa: N815
    kFingerCredentials = 0x4  # noqa: N815
    kLogging = 0x8  # noqa: N815
    kWeekDayAccessSchedules = 0x10  # noqa: N815
    kDoorPositionSensor = 0x20  # noqa: N815
    kFaceCredentials = 0x40  # noqa: N815
    kCredentialsOverTheAirAccess = 0x80  # noqa: N815
    kUser = 0x100  # noqa: N815
    kNotification = 0x200  # noqa: N815
    kYearDayAccessSchedules = 0x400  # noqa: N815
    kHolidaySchedules = 0x800  # noqa: N815


DISCOVERY_SCHEMAS = [
    MatterDiscoverySchema(
        platform=Platform.LOCK,
        entity_description=LockEntityDescription(key="MatterLock", name=None),
        entity_class=MatterLock,
        required_attributes=(clusters.DoorLock.Attributes.LockState,),
        optional_attributes=(clusters.DoorLock.Attributes.DoorState,),
    ),
]
