"""Repairs implementation for supervisor integration."""

from collections.abc import Callable, Coroutine
from types import MethodType
from typing import Any, override

from aiohasupervisor import SupervisorError
from aiohasupervisor.models import ContextType
import voluptuous as vol

from homeassistant.components.repairs import (
    ConfirmRepairFlow,
    RepairsFlow,
    RepairsFlowResult,
)
from homeassistant.const import ATTR_NAME
from homeassistant.core import HomeAssistant

from . import get_addons_list
from .const import (
    ATTR_SLUG,
    EXTRA_PLACEHOLDERS,
    ISSUE_KEY_ADDON_APP_PORT_CONFLICT,
    ISSUE_KEY_ADDON_BOOT_FAIL,
    ISSUE_KEY_ADDON_DEPRECATED,
    ISSUE_KEY_ADDON_DEPRECATED_ARCH,
    ISSUE_KEY_ADDON_DETACHED_ADDON_REMOVED,
    ISSUE_KEY_ADDON_PWNED,
    ISSUE_KEY_LEGACY_HOMEASSISTANT_FOLDER,
    ISSUE_KEY_SYSTEM_DOCKER_CONFIG,
    PLACEHOLDER_KEY_ADDON,
    PLACEHOLDER_KEY_ADDON_DOCUMENTATION,
    PLACEHOLDER_KEY_ADDON_INFO,
    PLACEHOLDER_KEY_COMPONENTS,
    PLACEHOLDER_KEY_PORT,
    PLACEHOLDER_KEY_REFERENCE,
)
from .coordinator import get_issues_info
from .handler import get_supervisor_client
from .issues import Issue, Suggestion

SUGGESTION_CONFIRMATION_REQUIRED = {
    "addon_execute_remove",
    "system_adopt_data_disk",
    "system_execute_reboot",
}


class SupervisorIssueRepairFlow(RepairsFlow):
    """Handler for an issue fixing flow."""

    _data: dict[str, Any] | None = None
    _issue: Issue | None = None

    def __init__(self, hass: HomeAssistant, issue_id: str) -> None:
        """Initialize repair flow."""
        self._issue_id = issue_id
        self._supervisor_client = get_supervisor_client(hass)
        super().__init__()

    @property
    def issue(self) -> Issue | None:
        """Get associated issue."""
        supervisor_issues = get_issues_info(self.hass)
        if not self._issue and supervisor_issues:
            self._issue = supervisor_issues.get_issue(self._issue_id)

        return self._issue

    @property
    def description_placeholders(self) -> dict[str, str] | None:
        """Get description placeholders for steps."""
        if not self.issue:
            return None

        if self.issue.key in EXTRA_PLACEHOLDERS:
            placeholders: dict[str, str] = EXTRA_PLACEHOLDERS[self.issue.key].copy()
        else:
            placeholders = {}

        if self.issue.reference:
            placeholders |= {PLACEHOLDER_KEY_REFERENCE: self.issue.reference}

        return placeholders or None

    def _async_form_for_suggestion(self, suggestion: Suggestion) -> RepairsFlowResult:
        """Return form for suggestion."""
        return self.async_show_form(
            step_id=suggestion.key,
            data_schema=vol.Schema({}),
            description_placeholders=self.description_placeholders,
            last_step=True,
        )

    async def async_step_init(self, _: None = None) -> RepairsFlowResult:
        """Handle the first step of a fix flow."""
        # Out of sync with supervisor, issue is resolved or not fixable. Remove it
        if not self.issue or not self.issue.suggestions:
            return self.async_create_entry(data={})

        # All suggestions have the same logic: Apply them in supervisor,
        # optionally with a confirmation step. Generating the required handler for each
        # allows for shared logic but screens can still be translated per step id.
        for suggestion in self.issue.suggestions:
            setattr(
                self,
                f"async_step_{suggestion.key}",
                MethodType(self._async_step(suggestion), self),
            )

        if len(self.issue.suggestions) > 1:
            return await self.async_step_fix_menu()

        # Always show a form for one suggestion to explain to user what's happening
        return self._async_form_for_suggestion(self.issue.suggestions[0])

    async def async_step_fix_menu(self, _: None = None) -> RepairsFlowResult:
        """Show the fix menu."""
        assert self.issue

        return self.async_show_menu(
            step_id="fix_menu",
            menu_options=[suggestion.key for suggestion in self.issue.suggestions],
            description_placeholders=self.description_placeholders,
        )

    async def _async_step_apply_suggestion(
        self, suggestion: Suggestion, confirmed: bool = False
    ) -> RepairsFlowResult:
        """Handle applying a suggestion as a flow step.

        Optionally request confirmation.
        """
        if not confirmed and suggestion.key in SUGGESTION_CONFIRMATION_REQUIRED:
            return self._async_form_for_suggestion(suggestion)

        try:
            await self._supervisor_client.resolution.apply_suggestion(suggestion.uuid)
        except SupervisorError:
            return self.async_abort(reason="apply_suggestion_fail")

        return self.async_create_entry(data={})

    @staticmethod
    def _async_step(
        suggestion: Suggestion,
    ) -> Callable[
        [SupervisorIssueRepairFlow, dict[str, str] | None],
        Coroutine[Any, Any, RepairsFlowResult],
    ]:
        """Generate a step handler for a suggestion."""

        async def _async_step(
            self: SupervisorIssueRepairFlow, user_input: dict[str, str] | None = None
        ) -> RepairsFlowResult:
            """Handle a flow step for a suggestion."""
            return await self._async_step_apply_suggestion(
                suggestion, confirmed=user_input is not None
            )

        return _async_step


class DockerConfigIssueRepairFlow(SupervisorIssueRepairFlow):
    """Handler for docker config issue fixing flow."""

    @property
    @override
    def description_placeholders(self) -> dict[str, str] | None:
        """Get description placeholders for steps."""
        placeholders = {PLACEHOLDER_KEY_COMPONENTS: ""}
        supervisor_issues = get_issues_info(self.hass)
        if supervisor_issues and self.issue:
            addons_list = get_addons_list(self.hass) or []
            components: list[str] = []
            for issue in supervisor_issues.issues:
                if issue.key == self.issue.key or issue.type != self.issue.type:
                    continue

                if issue.context == ContextType.CORE:
                    components.insert(0, "Home Assistant")
                elif issue.context == ContextType.ADDON:
                    components.append(
                        next(
                            (
                                addon[ATTR_NAME]
                                for addon in addons_list
                                if addon[ATTR_SLUG] == issue.reference
                            ),
                            issue.reference or "",
                        )
                    )

            placeholders[PLACEHOLDER_KEY_COMPONENTS] = "\n- ".join(components)

        return placeholders


class AddonIssueRepairFlow(SupervisorIssueRepairFlow):
    """Handler for addon issue fixing flows."""

    @property
    @override
    def description_placeholders(self) -> dict[str, str] | None:
        """Get description placeholders for steps."""
        placeholders: dict[str, str] = super().description_placeholders or {}
        if self.issue and self.issue.reference:
            addons_list = get_addons_list(self.hass) or []
            placeholders[PLACEHOLDER_KEY_ADDON] = self.issue.reference
            for addon in addons_list:
                if addon[ATTR_SLUG] == self.issue.reference:
                    placeholders[PLACEHOLDER_KEY_ADDON] = addon[ATTR_NAME]
                    break

        return placeholders or None


class DeprecatedAddonIssueRepairFlow(AddonIssueRepairFlow):
    """Handler for deprecated addon issue fixing flows."""

    @property
    @override
    def description_placeholders(self) -> dict[str, str] | None:
        """Get description placeholders for steps."""
        placeholders: dict[str, str] = super().description_placeholders or {}
        if self.issue and self.issue.reference:
            placeholders[PLACEHOLDER_KEY_ADDON_INFO] = (
                f"homeassistant://hassio/addon/{self.issue.reference}/info"
            )
            placeholders[PLACEHOLDER_KEY_ADDON_DOCUMENTATION] = (
                f"homeassistant://hassio/addon/{self.issue.reference}/documentation"
            )
        return placeholders or None


class AppPortConflictRepairFlow(AddonIssueRepairFlow):
    """Handler for app port conflict issue fixing flows."""

    @property
    @override
    def description_placeholders(self) -> dict[str, str] | None:
        """Get description placeholders for steps."""
        placeholders: dict[str, str] = super().description_placeholders or {}
        if self.issue and self.issue.reference_extra:
            placeholders[PLACEHOLDER_KEY_PORT] = str(self.issue.reference_extra["port"])
        return placeholders or None


async def async_create_fix_flow(
    hass: HomeAssistant,
    issue_id: str,
    data: dict[str, str | int | float | None] | None,
) -> RepairsFlow:
    """Create flow."""
    if issue_id == ISSUE_KEY_LEGACY_HOMEASSISTANT_FOLDER:
        return ConfirmRepairFlow()
    supervisor_issues = get_issues_info(hass)
    issue = supervisor_issues and supervisor_issues.get_issue(issue_id)
    if issue and issue.key == ISSUE_KEY_SYSTEM_DOCKER_CONFIG:
        return DockerConfigIssueRepairFlow(hass, issue_id)
    if issue and issue.key == ISSUE_KEY_ADDON_DEPRECATED:
        return DeprecatedAddonIssueRepairFlow(hass, issue_id)
    if issue and issue.key == ISSUE_KEY_ADDON_APP_PORT_CONFLICT:
        return AppPortConflictRepairFlow(hass, issue_id)
    if issue and issue.key in {
        ISSUE_KEY_ADDON_DETACHED_ADDON_REMOVED,
        ISSUE_KEY_ADDON_BOOT_FAIL,
        ISSUE_KEY_ADDON_PWNED,
        ISSUE_KEY_ADDON_DEPRECATED_ARCH,
    }:
        return AddonIssueRepairFlow(hass, issue_id)

    return SupervisorIssueRepairFlow(hass, issue_id)
