"""Test integration platform helpers."""

from collections.abc import Callable
from types import ModuleType
from unittest.mock import Mock, patch

import pytest

from homeassistant import loader
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.integration_platform import (
    async_process_integration_platforms,
)
from homeassistant.setup import ATTR_COMPONENT, EVENT_COMPONENT_LOADED

from tests.common import mock_platform


async def test_process_integration_platforms_with_wait(hass: HomeAssistant) -> None:
    """Test processing integrations."""
    loaded_platform = Mock()
    mock_platform(hass, "loaded.platform_to_check", loaded_platform)
    hass.config.components.add("loaded")

    event_platform = Mock()
    mock_platform(hass, "event.platform_to_check", event_platform)

    processed = []

    async def _process_platform(hass, domain, platform):
        """Process platform."""
        processed.append((domain, platform))

    await async_process_integration_platforms(
        hass, "platform_to_check", _process_platform, wait_for_platforms=True
    )
    # No block till done here, we want to make sure it waits for the platform

    assert len(processed) == 1
    assert processed[0][0] == "loaded"
    assert processed[0][1] == loaded_platform

    hass.bus.async_fire(EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: "event"})
    await hass.async_block_till_done()

    assert len(processed) == 2
    assert processed[1][0] == "event"
    assert processed[1][1] == event_platform

    hass.bus.async_fire(EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: "event"})
    await hass.async_block_till_done()

    # Firing again should not check again
    assert len(processed) == 2


async def test_process_integration_platforms(hass: HomeAssistant) -> None:
    """Test processing integrations."""
    loaded_platform = Mock()
    mock_platform(hass, "loaded.platform_to_check", loaded_platform)
    hass.config.components.add("loaded")

    event_platform = Mock()
    mock_platform(hass, "event.platform_to_check", event_platform)

    processed = []

    async def _process_platform(hass, domain, platform):
        """Process platform."""
        processed.append((domain, platform))

    await async_process_integration_platforms(
        hass, "platform_to_check", _process_platform
    )
    await hass.async_block_till_done()

    assert len(processed) == 1
    assert processed[0][0] == "loaded"
    assert processed[0][1] == loaded_platform

    hass.bus.async_fire(EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: "event"})
    await hass.async_block_till_done()

    assert len(processed) == 2
    assert processed[1][0] == "event"
    assert processed[1][1] == event_platform

    hass.bus.async_fire(EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: "event"})
    await hass.async_block_till_done()

    # Firing again should not check again
    assert len(processed) == 2


async def test_process_integration_platforms_import_fails(
    hass: HomeAssistant, caplog: pytest.LogCaptureFixture
) -> None:
    """Test processing integrations when one fails to import."""
    loaded_platform = Mock()
    mock_platform(hass, "loaded.platform_to_check", loaded_platform)
    hass.config.components.add("loaded")

    event_platform = Mock()
    mock_platform(hass, "event.platform_to_check", event_platform)

    processed = []

    async def _process_platform(hass, domain, platform):
        """Process platform."""
        processed.append((domain, platform))

    loaded_integration = await loader.async_get_integration(hass, "loaded")
    with patch.object(
        loaded_integration, "async_get_platform", side_effect=ImportError
    ):
        await async_process_integration_platforms(
            hass, "platform_to_check", _process_platform
        )
        await hass.async_block_till_done()

    assert len(processed) == 0
    assert "Unexpected error importing platform_to_check for loaded" in caplog.text

    hass.bus.async_fire(EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: "event"})
    await hass.async_block_till_done()

    assert len(processed) == 1
    assert processed[0][0] == "event"
    assert processed[0][1] == event_platform

    hass.bus.async_fire(EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: "event"})
    await hass.async_block_till_done()

    # Firing again should not check again
    assert len(processed) == 1


async def test_process_integration_platforms_import_fails_after_registered(
    hass: HomeAssistant, caplog: pytest.LogCaptureFixture
) -> None:
    """Test processing integrations when one fails to import."""
    loaded_platform = Mock()
    mock_platform(hass, "loaded.platform_to_check", loaded_platform)
    hass.config.components.add("loaded")

    event_platform = Mock()
    mock_platform(hass, "event.platform_to_check", event_platform)

    processed = []

    async def _process_platform(hass, domain, platform):
        """Process platform."""
        processed.append((domain, platform))

    await async_process_integration_platforms(
        hass, "platform_to_check", _process_platform
    )
    await hass.async_block_till_done()

    assert len(processed) == 1
    assert processed[0][0] == "loaded"
    assert processed[0][1] == loaded_platform

    event_integration = await loader.async_get_integration(hass, "event")
    with patch.object(
        event_integration, "async_get_platforms", side_effect=ImportError
    ), patch.object(event_integration, "get_platform_cached", return_value=None):
        hass.bus.async_fire(EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: "event"})
        await hass.async_block_till_done()

    assert len(processed) == 1
    assert "Unexpected error importing integration platforms for event" in caplog.text


@callback
def _process_platform_callback(
    hass: HomeAssistant, domain: str, platform: ModuleType
) -> None:
    """Process platform."""
    raise HomeAssistantError("Non-compliant platform")


async def _process_platform_coro(
    hass: HomeAssistant, domain: str, platform: ModuleType
) -> None:
    """Process platform."""
    raise HomeAssistantError("Non-compliant platform")


@pytest.mark.no_fail_on_log_exception
@pytest.mark.parametrize(
    "process_platform", [_process_platform_callback, _process_platform_coro]
)
async def test_process_integration_platforms_non_compliant(
    hass: HomeAssistant, caplog: pytest.LogCaptureFixture, process_platform: Callable
) -> None:
    """Test processing integrations using with a non-compliant platform."""
    loaded_platform = Mock()
    mock_platform(hass, "loaded_unique_880.platform_to_check", loaded_platform)
    hass.config.components.add("loaded_unique_880")

    event_platform = Mock()
    mock_platform(hass, "event_unique_990.platform_to_check", event_platform)

    processed = []

    await async_process_integration_platforms(
        hass, "platform_to_check", process_platform
    )
    await hass.async_block_till_done()

    assert len(processed) == 0
    assert "Exception in " in caplog.text
    assert "platform_to_check" in caplog.text
    assert "Non-compliant platform" in caplog.text
    assert "loaded_unique_880" in caplog.text
    caplog.clear()

    hass.bus.async_fire(EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: "event_unique_990"})
    await hass.async_block_till_done()

    assert "Exception in " in caplog.text
    assert "platform_to_check" in caplog.text
    assert "Non-compliant platform" in caplog.text
    assert "event_unique_990" in caplog.text

    assert len(processed) == 0


async def test_broken_integration(
    hass: HomeAssistant, caplog: pytest.LogCaptureFixture
) -> None:
    """Test handling an integration with a broken or missing manifest."""
    Mock()
    hass.config.components.add("loaded")

    event_platform = Mock()
    mock_platform(hass, "event.platform_to_check", event_platform)

    processed = []

    async def _process_platform(hass, domain, platform):
        """Process platform."""
        processed.append((domain, platform))

    await async_process_integration_platforms(
        hass, "platform_to_check", _process_platform
    )
    await hass.async_block_till_done()

    # This should never actually happen as the component cannot be
    # in hass.config.components without a loaded manifest
    assert len(processed) == 0


async def test_process_integration_platforms_no_integrations(
    hass: HomeAssistant,
) -> None:
    """Test processing integrations when no integrations are loaded."""
    event_platform = Mock()
    mock_platform(hass, "event.platform_to_check", event_platform)

    processed = []

    async def _process_platform(hass, domain, platform):
        """Process platform."""
        processed.append((domain, platform))

    await async_process_integration_platforms(
        hass, "platform_to_check", _process_platform
    )
    await hass.async_block_till_done()

    assert len(processed) == 0