"""Spook - Your homie."""

from __future__ import annotations

import re
from typing import TYPE_CHECKING, Any

from homeassistant.components import automation
from homeassistant.const import EVENT_COMPONENT_LOADED, EVENT_STATE_CHANGED
from homeassistant.core import Event, callback
from homeassistant.helpers import entity_registry as er

from ....const import LOGGER
from ....entity_filtering import (
    ENTITY_ID_PATTERN,
    async_extract_entities_from_config,
    async_extract_entities_from_template_string,
    async_filter_known_entity_ids_with_templates,
    async_get_all_entity_ids,
    is_template_string,
)
from ....repairs import AbstractSpookEntityComponentUnknownReferencesRepair

if TYPE_CHECKING:
    from collections.abc import Mapping

    from homeassistant.core import HomeAssistant


async def extract_template_entities_from_automation_entity(
    hass: HomeAssistant, entity: Any
) -> set[str]:
    """Extract entities from automation configuration using Template analysis.

    This function finds template strings in automation configuration and creates
    Template objects to extract entity references using Template.async_render_to_info().
    This provides more comprehensive entity detection than regex-based parsing alone.
    """
    # Get the automation configuration
    config = None
    if hasattr(entity, "raw_config") and entity.raw_config:
        config = entity.raw_config
    else:
        return set()

    return await async_extract_entities_from_config(hass, config)


async def extract_entities_from_automation_config(
    hass: HomeAssistant, config: dict[str, Any]
) -> set[str]:
    """Extract entity IDs from automation configuration."""
    entities = set()

    if not isinstance(config, dict):
        return entities

    # Extract entities from trigger config
    for key in ("trigger", "triggers"):
        if key in config:
            entities.update(
                await extract_entities_from_trigger_config(hass, config[key])
            )

    # Extract entities from condition config
    for key in ("condition", "conditions"):
        if key in config:
            entities.update(
                await extract_entities_from_condition_config(hass, config[key])
            )

    # Extract entities from action config
    for key in ("action", "actions"):
        if key in config:
            entities.update(
                await extract_entities_from_action_config(hass, config[key])
            )

    return entities


async def extract_entities_from_trigger_config(
    hass: HomeAssistant, config: dict[str, Any] | list
) -> set[str]:
    """Extract entity IDs from trigger configuration."""
    entities = set()

    if not config:
        return entities

    if isinstance(config, list):
        for item in config:
            entities.update(await extract_entities_from_trigger_config(hass, item))
        return entities

    if not isinstance(config, dict):
        return entities

    # Entity ID fields in triggers
    for key in ("entity_id", "device_id"):
        if key in config:
            entities.update(await extract_entities_from_value(hass, config[key]))

    # Zone trigger has zone field
    if "zone" in config:
        entities.update(await extract_entities_from_value(hass, config["zone"]))

    # Extract from nested configs
    for value in config.values():
        if isinstance(value, (dict, list)):
            entities.update(await extract_entities_from_trigger_config(hass, value))

    return entities


def extract_event_types_from_trigger_config(config: dict[str, Any] | list) -> set[str]:
    """Extract event types from trigger configuration."""
    event_types = set()

    if not config:
        return event_types

    if isinstance(config, list):
        for item in config:
            event_types.update(extract_event_types_from_trigger_config(item))
        return event_types

    if not isinstance(config, dict):
        return event_types

    value = config.get("event_type")
    if isinstance(value, str):
        event_types.add(value)
    elif isinstance(value, list):
        event_types.update(item for item in value if isinstance(item, str))

    for value in config.values():
        if isinstance(value, (dict, list)):
            event_types.update(extract_event_types_from_trigger_config(value))

    return event_types


async def extract_entities_from_condition_config(
    hass: HomeAssistant, config: dict[str, Any] | list
) -> set[str]:
    """Extract entity IDs from condition configuration."""
    entities = set()

    if not config:
        return entities

    if isinstance(config, list):
        for item in config:
            entities.update(await extract_entities_from_condition_config(hass, item))
        return entities

    if not isinstance(config, dict):
        return entities

    # Entity ID fields in conditions
    for key in ("entity_id", "device_id", "zone"):
        if key in config:
            entities.update(await extract_entities_from_value(hass, config[key]))

    # Extract from nested configs
    for value in config.values():
        if isinstance(value, (dict, list)):
            entities.update(await extract_entities_from_condition_config(hass, value))

    return entities


async def extract_entities_from_action_config(
    hass: HomeAssistant, config: dict[str, Any] | list
) -> set[str]:
    """Extract entity IDs from action configuration."""
    entities = set()

    if not config:
        return entities

    if isinstance(config, list):
        for item in config:
            entities.update(await extract_entities_from_action_config(hass, item))
        return entities

    if not isinstance(config, dict):
        return entities

    # Extract entity IDs from direct fields
    entities.update(await _extract_entities_from_action_fields(hass, config))

    # Extract entities from target configuration
    entities.update(await _extract_entities_from_target(hass, config))

    # Extract entities from service data
    entities.update(await _extract_entities_from_service_data(hass, config))

    # Extract from nested configs (like if/then/else, repeat, etc.)
    entities.update(await _extract_entities_from_nested_configs(hass, config))

    return entities


async def _extract_entities_from_action_fields(
    hass: HomeAssistant, config: dict[str, Any]
) -> set[str]:
    """Extract entities from direct action fields."""
    entities = set()
    for key in ("entity_id", "device_id"):
        if key in config:
            entities.update(await extract_entities_from_value(hass, config[key]))
    return entities


async def _extract_entities_from_target(
    hass: HomeAssistant, config: dict[str, Any]
) -> set[str]:
    """Extract entities from target configuration."""
    entities = set()
    if "target" in config and isinstance(config["target"], dict):
        target = config["target"]
        for key in ("entity_id", "device_id", "area_id", "label_id"):
            if key in target:
                entities.update(await extract_entities_from_value(hass, target[key]))
    return entities


def _get_action_service(config: dict[str, Any]) -> str | None:
    """Return the service/action name configured for an action."""
    service = config.get("service", config.get("action"))
    return service if isinstance(service, str) else None


def _should_skip_service_data_value(
    service: str | None,
    key: str,
) -> bool:
    """Return if a service data value should not be scanned for entity IDs."""
    return service is not None and service.startswith("notify.") and key == "target"


async def _extract_entities_from_service_data(
    hass: HomeAssistant, config: dict[str, Any]
) -> set[str]:
    """Extract entities from service data."""
    entities = set()
    if "data" in config:
        data_value = config["data"]
        if isinstance(data_value, str):
            # data field is a template string itself
            entities.update(await extract_entities_from_value(hass, data_value))
        elif isinstance(data_value, dict):
            service = _get_action_service(config)
            # data field is a dictionary, process all its values
            for key, value in data_value.items():
                if _should_skip_service_data_value(service, key):
                    continue
                entities.update(await extract_entities_from_value(hass, value))
    return entities


async def _extract_entities_from_nested_configs(
    hass: HomeAssistant, config: dict[str, Any]
) -> set[str]:
    """Extract entities from nested configurations."""
    entities = set()
    for value in config.values():
        if isinstance(value, (dict, list)):
            entities.update(await extract_entities_from_action_config(hass, value))
    return entities


async def extract_entities_from_value(hass: HomeAssistant, value: Any) -> set[str]:
    """Extract entity IDs from a configuration value."""
    entities = set()

    if isinstance(value, str):
        # Check if it's a template string using util.is_template_string
        if is_template_string(value):
            # Process as template to extract entity references
            try:
                template_entities = await async_extract_entities_from_template_string(
                    hass, value
                )
                entities.update(template_entities)
            # pylint: disable-next=broad-exception-caught
            except Exception as exc:  # noqa: BLE001 - Keep broad for unexpected template issues
                LOGGER.debug(
                    "Failed to extract entities from template: %s, error: %s",
                    value,
                    exc,
                )
        elif re.match(rf"^{ENTITY_ID_PATTERN}$", value):
            # Check if it matches the entity ID pattern with known domains
            entities.add(value)
    elif isinstance(value, list):
        for item in value:
            entities.update(await extract_entities_from_value(hass, item))
    elif (
        isinstance(value, dict)
        and "entity" in value
        and isinstance(value["entity"], str)
    ):
        # Handle entity dict format like {"entity": "light.living_room"}
        entities.add(value["entity"])

    return entities


class SpookRepair(AbstractSpookEntityComponentUnknownReferencesRepair):
    """Spook repair tries to find unknown referenced entity in automations."""

    domain = automation.DOMAIN
    repair = "automation_unknown_entity_references"
    inspect_events = {
        EVENT_COMPONENT_LOADED,
        er.EVENT_ENTITY_REGISTRY_UPDATED,
    }
    inspect_config_entry_changed = True
    inspect_on_reload = True

    unavailable_entity_class = automation.UnavailableAutomationEntity
    entity_label = "automation"
    reference_label = "entities"
    edit_url_pattern = "/config/automation/edit/{unique_id}"

    _known_entity_ids: set[str]

    async def async_activate(self) -> None:
        """Activate the repair."""
        await super().async_activate()

        @callback
        def _state_entity_changed(event_data: Mapping[str, Any]) -> bool:
            """Return if a state entity was added or removed."""
            return (
                event_data.get("old_state") is None
                or event_data.get("new_state") is None
            )

        @callback
        def _async_call_inspect_debouncer(_: Event) -> None:
            """Trigger an inspection when a state entity is added or removed."""
            self.inspect_debouncer.async_schedule_call()

        self._event_subs.add(
            self.hass.bus.async_listen(
                EVENT_STATE_CHANGED,
                _async_call_inspect_debouncer,
                event_filter=_state_entity_changed,
            ),
        )

    async def _async_setup_inspection(self) -> None:
        """Cache known entity IDs (including ALL/NONE) for this inspection cycle."""
        self._known_entity_ids = async_get_all_entity_ids(
            self.hass, include_all_none=True
        )

    def _should_inspect_entity(self, entity: Any) -> bool:
        """Skip disabled automations."""
        return entity.enabled

    async def _async_compute_unknown_references(self, entity: Any) -> set[str]:
        """Return unknown entity IDs referenced by ``entity`` (incl. templates)."""
        all_entities = set(entity.referenced_entities)

        # Also extract entities directly from raw configuration if available
        if hasattr(entity, "raw_config") and entity.raw_config:
            all_entities.update(
                await extract_entities_from_automation_config(
                    self.hass, entity.raw_config
                )
            )
            for key in ("trigger", "triggers"):
                all_entities.difference_update(
                    extract_event_types_from_trigger_config(entity.raw_config.get(key))
                )

        # Extract entities from Template objects within the automation entity
        all_entities.update(
            await extract_template_entities_from_automation_entity(self.hass, entity)
        )

        return await async_filter_known_entity_ids_with_templates(
            self.hass,
            entity_ids=all_entities,
            known_entity_ids=self._known_entity_ids,
        )
