"""Update platform for Supervisor."""

from __future__ import annotations

import re
from typing import Any

from aiohasupervisor import SupervisorError
from aiohasupervisor.models import Job
from awesomeversion import AwesomeVersion, AwesomeVersionStrategy

from homeassistant.components.update import (
    UpdateEntity,
    UpdateEntityDescription,
    UpdateEntityFeature,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_ICON, ATTR_NAME
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback

from .const import (
    ADDONS_COORDINATOR,
    ATTR_AUTO_UPDATE,
    ATTR_VERSION,
    ATTR_VERSION_LATEST,
    DATA_KEY_ADDONS,
    DATA_KEY_CORE,
    DATA_KEY_OS,
    DATA_KEY_SUPERVISOR,
)
from .entity import (
    HassioAddonEntity,
    HassioCoreEntity,
    HassioOSEntity,
    HassioSupervisorEntity,
)
from .jobs import JobSubscription
from .update_helper import update_addon, update_core, update_os

ENTITY_DESCRIPTION = UpdateEntityDescription(
    translation_key="update",
    key=ATTR_VERSION_LATEST,
)


async def async_setup_entry(
    hass: HomeAssistant,
    config_entry: ConfigEntry,
    async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
    """Set up Supervisor update based on a config entry."""
    coordinator = hass.data[ADDONS_COORDINATOR]

    entities = [
        SupervisorSupervisorUpdateEntity(
            coordinator=coordinator,
            entity_description=ENTITY_DESCRIPTION,
        ),
        SupervisorCoreUpdateEntity(
            coordinator=coordinator,
            entity_description=ENTITY_DESCRIPTION,
        ),
    ]

    entities.extend(
        SupervisorAddonUpdateEntity(
            addon=addon,
            coordinator=coordinator,
            entity_description=ENTITY_DESCRIPTION,
        )
        for addon in coordinator.data[DATA_KEY_ADDONS].values()
    )

    if coordinator.is_hass_os:
        entities.append(
            SupervisorOSUpdateEntity(
                coordinator=coordinator,
                entity_description=ENTITY_DESCRIPTION,
            )
        )

    async_add_entities(entities)


class SupervisorAddonUpdateEntity(HassioAddonEntity, UpdateEntity):
    """Update entity to handle updates for the Supervisor add-ons."""

    _attr_supported_features = (
        UpdateEntityFeature.INSTALL
        | UpdateEntityFeature.BACKUP
        | UpdateEntityFeature.RELEASE_NOTES
        | UpdateEntityFeature.PROGRESS
    )

    @property
    def _addon_data(self) -> dict:
        """Return the add-on data."""
        return self.coordinator.data[DATA_KEY_ADDONS][self._addon_slug]

    @property
    def auto_update(self) -> bool:
        """Return true if auto-update is enabled for the add-on."""
        return self._addon_data[ATTR_AUTO_UPDATE]

    @property
    def title(self) -> str | None:
        """Return the title of the update."""
        return self._addon_data[ATTR_NAME]

    @property
    def latest_version(self) -> str | None:
        """Latest version available for install."""
        return self._addon_data[ATTR_VERSION_LATEST]

    @property
    def installed_version(self) -> str | None:
        """Version installed and in use."""
        return self._addon_data[ATTR_VERSION]

    @property
    def entity_picture(self) -> str | None:
        """Return the icon of the add-on if any."""
        if not self.available:
            return None
        if self._addon_data[ATTR_ICON]:
            return f"/api/hassio/addons/{self._addon_slug}/icon"
        return None

    async def async_release_notes(self) -> str | None:
        """Return the release notes for the update."""
        if (
            changelog := await self.coordinator.get_changelog(self._addon_slug)
        ) is None:
            return None

        if self.latest_version is None or self.installed_version is None:
            return changelog

        regex_pattern = re.compile(
            rf"^#* {re.escape(self.latest_version)}\n(?:^(?!#* {re.escape(self.installed_version)}).*\n)*",
            re.MULTILINE,
        )
        match = regex_pattern.search(changelog)
        return match.group(0) if match else changelog

    async def async_install(
        self,
        version: str | None = None,
        backup: bool = False,
        **kwargs: Any,
    ) -> None:
        """Install an update."""
        self._attr_in_progress = True
        self.async_write_ha_state()
        await update_addon(
            self.hass, self._addon_slug, backup, self.title, self.installed_version
        )
        await self.coordinator.async_refresh()

    @callback
    def _update_job_changed(self, job: Job) -> None:
        """Process update for this entity's update job."""
        if job.done is False:
            self._attr_in_progress = True
            self._attr_update_percentage = job.progress
        else:
            self._attr_in_progress = False
            self._attr_update_percentage = None
        self.async_write_ha_state()

    async def async_added_to_hass(self) -> None:
        """Subscribe to progress updates."""
        await super().async_added_to_hass()
        self.async_on_remove(
            self.coordinator.jobs.subscribe(
                JobSubscription(
                    self._update_job_changed,
                    name="addon_manager_update",
                    reference=self._addon_slug,
                )
            )
        )


class SupervisorOSUpdateEntity(HassioOSEntity, UpdateEntity):
    """Update entity to handle updates for the Home Assistant Operating System."""

    _attr_supported_features = (
        UpdateEntityFeature.INSTALL
        | UpdateEntityFeature.SPECIFIC_VERSION
        | UpdateEntityFeature.BACKUP
    )
    _attr_title = "Home Assistant Operating System"

    @property
    def latest_version(self) -> str:
        """Return the latest version."""
        return self.coordinator.data[DATA_KEY_OS][ATTR_VERSION_LATEST]

    @property
    def installed_version(self) -> str:
        """Return the installed version."""
        return self.coordinator.data[DATA_KEY_OS][ATTR_VERSION]

    @property
    def entity_picture(self) -> str | None:
        """Return the icon of the entity."""
        return "https://brands.home-assistant.io/homeassistant/icon.png"

    @property
    def release_url(self) -> str | None:
        """URL to the full release notes of the latest version available."""
        version = AwesomeVersion(self.latest_version)
        if version.dev or version.strategy == AwesomeVersionStrategy.UNKNOWN:
            return "https://github.com/home-assistant/operating-system/commits/dev"
        return (
            f"https://github.com/home-assistant/operating-system/releases/tag/{version}"
        )

    async def async_install(
        self, version: str | None, backup: bool, **kwargs: Any
    ) -> None:
        """Install an update."""
        await update_os(self.hass, version, backup)


class SupervisorSupervisorUpdateEntity(HassioSupervisorEntity, UpdateEntity):
    """Update entity to handle updates for the Home Assistant Supervisor."""

    _attr_supported_features = UpdateEntityFeature.INSTALL
    _attr_title = "Home Assistant Supervisor"

    @property
    def latest_version(self) -> str:
        """Return the latest version."""
        return self.coordinator.data[DATA_KEY_SUPERVISOR][ATTR_VERSION_LATEST]

    @property
    def installed_version(self) -> str:
        """Return the installed version."""
        return self.coordinator.data[DATA_KEY_SUPERVISOR][ATTR_VERSION]

    @property
    def auto_update(self) -> bool:
        """Return true if auto-update is enabled for supervisor."""
        return self.coordinator.data[DATA_KEY_SUPERVISOR][ATTR_AUTO_UPDATE]

    @property
    def release_url(self) -> str | None:
        """URL to the full release notes of the latest version available."""
        version = AwesomeVersion(self.latest_version)
        if version.dev or version.strategy == AwesomeVersionStrategy.UNKNOWN:
            return "https://github.com/home-assistant/supervisor/commits/main"
        return f"https://github.com/home-assistant/supervisor/releases/tag/{version}"

    @property
    def entity_picture(self) -> str | None:
        """Return the icon of the entity."""
        return "https://brands.home-assistant.io/hassio/icon.png"

    async def async_install(
        self, version: str | None, backup: bool, **kwargs: Any
    ) -> None:
        """Install an update."""
        try:
            await self.coordinator.supervisor_client.supervisor.update()
        except SupervisorError as err:
            raise HomeAssistantError(
                f"Error updating Home Assistant Supervisor: {err}"
            ) from err


class SupervisorCoreUpdateEntity(HassioCoreEntity, UpdateEntity):
    """Update entity to handle updates for Home Assistant Core."""

    _attr_supported_features = (
        UpdateEntityFeature.INSTALL
        | UpdateEntityFeature.SPECIFIC_VERSION
        | UpdateEntityFeature.BACKUP
        | UpdateEntityFeature.PROGRESS
    )
    _attr_title = "Home Assistant Core"

    @property
    def latest_version(self) -> str:
        """Return the latest version."""
        return self.coordinator.data[DATA_KEY_CORE][ATTR_VERSION_LATEST]

    @property
    def installed_version(self) -> str:
        """Return the installed version."""
        return self.coordinator.data[DATA_KEY_CORE][ATTR_VERSION]

    @property
    def entity_picture(self) -> str | None:
        """Return the icon of the entity."""
        return "https://brands.home-assistant.io/homeassistant/icon.png"

    @property
    def release_url(self) -> str | None:
        """URL to the full release notes of the latest version available."""
        version = AwesomeVersion(self.latest_version)
        if version.dev:
            return "https://github.com/home-assistant/core/commits/dev"
        return f"https://{'rc' if version.beta else 'www'}.home-assistant.io/latest-release-notes/"

    async def async_install(
        self, version: str | None, backup: bool, **kwargs: Any
    ) -> None:
        """Install an update."""
        self._attr_in_progress = True
        self.async_write_ha_state()
        await update_core(self.hass, version, backup)

    @callback
    def _update_job_changed(self, job: Job) -> None:
        """Process update for this entity's update job."""
        if job.done is False:
            self._attr_in_progress = True
            self._attr_update_percentage = job.progress
        else:
            self._attr_in_progress = False
            self._attr_update_percentage = None
        self.async_write_ha_state()

    async def async_added_to_hass(self) -> None:
        """Subscribe to progress updates."""
        await super().async_added_to_hass()
        self.async_on_remove(
            self.coordinator.jobs.subscribe(
                JobSubscription(
                    self._update_job_changed, name="home_assistant_core_update"
                )
            )
        )
