"""Tests for zeroconf repair issues."""

from unittest.mock import patch

import pytest
from zeroconf import ServiceStateChange
from zeroconf.asyncio import AsyncServiceInfo

from homeassistant.components.homeassistant import DOMAIN as HOMEASSISTANT_DOMAIN
from homeassistant.components.repairs import DOMAIN as REPAIRS_DOMAIN
from homeassistant.components.zeroconf import DOMAIN, discovery, repairs
from homeassistant.components.zeroconf.discovery import ZEROCONF_TYPE
from homeassistant.const import EVENT_HOMEASSISTANT_STARTED
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from homeassistant.helpers import instance_id, issue_registry as ir
from homeassistant.setup import async_setup_component

from .test_init import service_update_mock

from tests.components.repairs import (
    async_process_repairs_platforms,
    process_repair_fix_flow,
    start_repair_fix_flow,
)
from tests.typing import ClientSessionGenerator


def service_state_change_mock(
    zeroconf,
    services,
    handlers,
    *,
    state_change: ServiceStateChange = ServiceStateChange.Removed,
) -> None:
    """Call service update handler."""
    for service in services:
        handlers[0](zeroconf, service, f"_name.{service}", state_change)


def _get_hass_service_info_mock(
    service_type: str,
    name: str,
    *,
    instance_id="abc123",
) -> AsyncServiceInfo:
    """Return service info for Home Assistant instance."""
    return AsyncServiceInfo(
        ZEROCONF_TYPE,
        name,
        addresses=[b"\n\x00\x00\x01"],
        port=8123,
        weight=0,
        priority=0,
        server="other-host.local.",
        properties={
            "base_url": "http://10.0.0.1:8123",
            "external_url": None,
            "internal_url": "http://10.0.0.1:8123",
            "location_name": "Home",
            "requires_api_password": "True",
            "uuid": instance_id,
            "version": "2025.9.0.dev0",
        },
    )


@pytest.mark.usefixtures("mock_async_zeroconf")
async def test_instance_id_conflict_creates_repair_issue_remove(
    hass: HomeAssistant, issue_registry: ir.IssueRegistry
) -> None:
    """Test repair issue on instance ID conflict removed on disappear."""
    with (
        patch("homeassistant.helpers.instance_id.async_get", return_value="abc123"),
        patch.object(
            discovery, "AsyncServiceBrowser", side_effect=service_update_mock
        ) as mock_browser,
        patch.object(hass.config_entries.flow, "async_init"),
        patch(
            "homeassistant.components.zeroconf.discovery.AsyncServiceInfo",
            side_effect=_get_hass_service_info_mock,
        ),
    ):
        assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}})
        hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
        await hass.async_block_till_done()

        issue = issue_registry.async_get_issue(
            domain="zeroconf", issue_id="duplicate_instance_id"
        )
        assert issue
        assert issue.severity == ir.IssueSeverity.ERROR
        assert issue.translation_key == "duplicate_instance_id"
        assert issue.translation_placeholders == {
            "other_host_url": "other-host.local",
            "other_ip": "10.0.0.1",
            "instance_id": "abc123",
        }

        # Now test that the issue is removed when the service goes away
        service_state_change_mock(
            mock_browser.call_args[0][0],
            [ZEROCONF_TYPE],
            mock_browser.call_args[1]["handlers"],
        )
        assert (
            issue_registry.async_get_issue(
                domain="zeroconf", issue_id="duplicate_instance_id"
            )
            is None
        )


@pytest.mark.usefixtures("mock_async_zeroconf")
async def test_instance_id_conflict_creates_repair_issue_changing_id(
    hass: HomeAssistant, issue_registry: ir.IssueRegistry
) -> None:
    """Test repair issue on instance ID conflict removed on ID change."""
    with (
        patch("homeassistant.helpers.instance_id.async_get", return_value="abc123"),
        patch.object(
            discovery, "AsyncServiceBrowser", side_effect=service_update_mock
        ) as mock_browser,
        patch.object(hass.config_entries.flow, "async_init"),
        patch(
            "homeassistant.components.zeroconf.discovery.AsyncServiceInfo",
            side_effect=_get_hass_service_info_mock,
        ),
    ):
        assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}})
        hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
        await hass.async_block_till_done()

        issue = issue_registry.async_get_issue(
            domain="zeroconf", issue_id="duplicate_instance_id"
        )
        assert issue
        assert issue.severity == ir.IssueSeverity.ERROR
        assert issue.translation_key == "duplicate_instance_id"
        assert issue.translation_placeholders == {
            "other_host_url": "other-host.local",
            "other_ip": "10.0.0.1",
            "instance_id": "abc123",
        }

        with (
            patch(
                "homeassistant.components.zeroconf.discovery.AsyncServiceInfo",
                side_effect=lambda service_type, name: _get_hass_service_info_mock(
                    service_type, name, instance_id="different-id"
                ),
            ),
        ):
            # Now test that the issue is removed when the service goes away
            service_state_change_mock(
                mock_browser.call_args[0][0],
                [ZEROCONF_TYPE],
                mock_browser.call_args[1]["handlers"],
                state_change=ServiceStateChange.Updated,
            )
            assert (
                issue_registry.async_get_issue(
                    domain="zeroconf", issue_id="duplicate_instance_id"
                )
                is None
            )


@pytest.mark.usefixtures("mock_async_zeroconf")
async def test_instance_id_no_repair_issue_own_ip(
    hass: HomeAssistant, issue_registry: ir.IssueRegistry
) -> None:
    """Test no repair issue when other instance ID matches our IP."""
    with (
        patch("homeassistant.helpers.instance_id.async_get", return_value="abc123"),
        patch.object(discovery, "AsyncServiceBrowser", side_effect=service_update_mock),
        patch.object(hass.config_entries.flow, "async_init"),
        patch(
            "homeassistant.components.zeroconf.discovery.AsyncServiceInfo",
            side_effect=_get_hass_service_info_mock,
        ),
        patch(
            "homeassistant.components.network.async_get_announce_addresses",
            return_value=["10.0.0.1", "10.0.0.2"],
        ),
    ):
        assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}})
        hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
        await hass.async_block_till_done()

        assert (
            issue_registry.async_get_issue(
                domain="zeroconf", issue_id="duplicate_instance_id"
            )
            is None
        )


@pytest.mark.usefixtures("mock_async_zeroconf")
async def test_instance_id_no_conflict_no_repair_issue(
    hass: HomeAssistant, issue_registry: ir.IssueRegistry
) -> None:
    """Test that a repair issue is not created when no instance ID conflict exists."""
    with (
        patch("homeassistant.helpers.instance_id.async_get", return_value="xyz123"),
        patch.object(discovery, "AsyncServiceBrowser", side_effect=service_update_mock),
        patch.object(hass.config_entries.flow, "async_init"),
        patch(
            "homeassistant.components.zeroconf.discovery.AsyncServiceInfo",
            side_effect=_get_hass_service_info_mock,
        ),
        patch("homeassistant.helpers.issue_registry.async_create_issue"),
    ):
        assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}})
        hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
        await hass.async_block_till_done()

        assert (
            issue_registry.async_get_issue(
                domain="zeroconf", issue_id="duplicate_instance_id"
            )
            is None
        )


async def test_create_fix_flow_raises_on_unknown_issue_id(hass: HomeAssistant) -> None:
    """Test create_fix_flow raises on unknown issue_id."""

    with pytest.raises(ValueError):
        await repairs.async_create_fix_flow(hass, "no_such_issue", None)


@pytest.mark.usefixtures("mock_async_zeroconf")
async def test_duplicate_repair_issue_repair_flow(
    hass: HomeAssistant,
    hass_client: ClientSessionGenerator,
    issue_registry: ir.IssueRegistry,
) -> None:
    """Test desired flow of the fix flow for duplicate instance ID."""
    assert await async_setup_component(hass, REPAIRS_DOMAIN, {REPAIRS_DOMAIN: {}})
    assert await async_setup_component(hass, HOMEASSISTANT_DOMAIN, {})
    await async_process_repairs_platforms(hass)

    with (
        patch("homeassistant.helpers.instance_id.async_get", return_value="abc123"),
        patch.object(discovery, "AsyncServiceBrowser", side_effect=service_update_mock),
        patch.object(hass.config_entries.flow, "async_init"),
        patch(
            "homeassistant.components.zeroconf.discovery.AsyncServiceInfo",
            side_effect=_get_hass_service_info_mock,
        ),
        patch.object(
            instance_id, "async_recreate", return_value="new-uuid"
        ) as mock_recreate,
        patch("homeassistant.config.async_check_ha_config_file", return_value=None),
        patch("homeassistant.core.HomeAssistant.async_stop", return_value=None),
    ):
        assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}})
        hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
        await hass.async_block_till_done()

        issue = issue_registry.async_get_issue(
            domain="zeroconf", issue_id="duplicate_instance_id"
        )
        assert issue is not None

        client = await hass_client()

        result = await start_repair_fix_flow(client, DOMAIN, issue.issue_id)

        flow_id = result["flow_id"]
        assert result["type"] == FlowResultType.FORM
        assert result["step_id"] == "confirm_recreate"

        result = await process_repair_fix_flow(client, flow_id, json={})
        assert result["type"] == "create_entry"

        await hass.async_block_till_done()

        assert mock_recreate.called
