"""Provides the coordinator for a LOQED lock."""

import asyncio
import logging
from typing import TypedDict, override

import aiohttp
from aiohttp.web import Request
from loqedAPI import loqed

from homeassistant.components import cloud, webhook
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_NAME, CONF_WEBHOOK_ID
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator

from .const import CONF_CLOUDHOOK_URL, DOMAIN

_LOGGER = logging.getLogger(__name__)

type LoqedConfigEntry = ConfigEntry[LoqedDataCoordinator]


class BatteryMessage(TypedDict):
    """Properties in a battery update message."""

    mac_wifi: str
    mac_ble: str
    battery_type: str
    battery_percentage: int


class StateReachedMessage(TypedDict):
    """Properties in a battery update message."""

    requested_state: str
    requested_state_numeric: int
    event_type: str
    key_local_id: int
    mac_wifi: str
    mac_ble: str


class TransitionMessage(TypedDict):
    """Properties in a battery update message."""

    go_to_state: str
    go_to_state_numeric: int
    event_type: str
    key_local_id: int
    mac_wifi: str
    mac_ble: str


class StatusMessage(TypedDict):
    """Properties returned by the status endpoint of the bridhge."""

    battery_percentage: int
    battery_type: str
    battery_type_numeric: int
    battery_voltage: float
    bolt_state: str
    bolt_state_numeric: int
    bridge_mac_wifi: str
    bridge_mac_ble: str
    lock_online: int
    webhooks_number: int
    ip_address: str
    up_timestamp: int
    wifi_strength: int
    ble_strength: int


class LoqedDataCoordinator(DataUpdateCoordinator[StatusMessage]):
    """Data update coordinator for the loqed platform."""

    config_entry: LoqedConfigEntry

    def __init__(
        self,
        hass: HomeAssistant,
        config_entry: LoqedConfigEntry,
        api: loqed.LoqedAPI,
        lock: loqed.Lock,
    ) -> None:
        """Initialize the Loqed Data Update coordinator."""
        super().__init__(hass, _LOGGER, config_entry=config_entry, name="Loqed sensors")
        self._api = api
        self.lock = lock
        self.device_name = config_entry.data[CONF_NAME]

    @override
    async def _async_update_data(self) -> StatusMessage:
        """Fetch data from API endpoint."""
        async with asyncio.timeout(10):
            return await self._api.async_get_lock_details()

    async def _handle_webhook(
        self, hass: HomeAssistant, webhook_id: str, request: Request
    ) -> None:
        """Handle incoming Loqed messages."""
        _LOGGER.debug("Callback received: %s", request.headers)
        received_ts = request.headers["TIMESTAMP"]
        received_hash = request.headers["HASH"]
        body = await request.text()

        _LOGGER.debug("Callback body: %s", body)

        event_data = await self.lock.receiveWebhook(body, received_hash, received_ts)
        if "error" in event_data:
            _LOGGER.warning("Incorrect callback received:: %s", event_data)
            return

        self.async_update_listeners()

    async def ensure_webhooks(self) -> None:
        """Register webhook on LOQED bridge."""
        webhook_id = self.config_entry.data[CONF_WEBHOOK_ID]

        webhook.async_register(
            self.hass, DOMAIN, "Loqed", webhook_id, self._handle_webhook
        )

        @callback
        def _async_unregister_webhook() -> None:
            webhook.async_unregister(self.hass, webhook_id)

        self.config_entry.async_on_unload(_async_unregister_webhook)

        if cloud.async_active_subscription(self.hass):
            webhook_url = await async_cloudhook_generate_url(
                self.hass, self.config_entry
            )
        else:
            webhook_url = webhook.async_generate_url(
                self.hass, self.config_entry.data[CONF_WEBHOOK_ID]
            )

        _LOGGER.debug("Webhook URL: %s", webhook_url)

        webhooks = await self.lock.getWebhooks()

        webhook_index = next(
            (x["id"] for x in webhooks if x["url"] == webhook_url), None
        )

        if not webhook_index:
            await self.lock.registerWebhook(webhook_url)
            webhooks = await self.lock.getWebhooks()
            webhook_index = next(x["id"] for x in webhooks if x["url"] == webhook_url)

            _LOGGER.debug("Webhook got index %s", webhook_index)

    async def remove_webhooks(self) -> None:
        """Remove webhook from LOQED bridge."""
        webhook_id = self.config_entry.data[CONF_WEBHOOK_ID]

        if CONF_CLOUDHOOK_URL in self.config_entry.data:
            webhook_url = self.config_entry.data[CONF_CLOUDHOOK_URL]
        else:
            webhook_url = webhook.async_generate_url(self.hass, webhook_id)

        _LOGGER.debug("Webhook URL: %s", webhook_url)

        try:
            webhooks = await self.lock.getWebhooks()

            webhook_index = next(
                (x["id"] for x in webhooks if x["url"] == webhook_url), None
            )

            if webhook_index:
                await self.lock.deleteWebhook(webhook_index)
        except (TimeoutError, aiohttp.ClientError) as err:
            _LOGGER.warning(
                "Could not remove webhook from LOQED bridge; the bridge may be offline. Continuing to unload the entry anyway: %s",
                err,
            )


async def async_cloudhook_generate_url(
    hass: HomeAssistant, entry: LoqedConfigEntry
) -> str:
    """Generate the full URL for a webhook_id."""
    if CONF_CLOUDHOOK_URL not in entry.data:
        webhook_url = await cloud.async_create_cloudhook(
            hass, entry.data[CONF_WEBHOOK_ID]
        )
        data = {**entry.data, CONF_CLOUDHOOK_URL: webhook_url}
        hass.config_entries.async_update_entry(entry, data=data)
        return webhook_url
    return str(entry.data[CONF_CLOUDHOOK_URL])
