"""Entities queries for logbook."""
from __future__ import annotations

from collections.abc import Iterable
from datetime import datetime as dt

import sqlalchemy
from sqlalchemy import lambda_stmt, select, union_all
from sqlalchemy.orm import Query
from sqlalchemy.sql.lambdas import StatementLambdaElement
from sqlalchemy.sql.selectable import CTE, CompoundSelect

from homeassistant.components.recorder.models import (
    ENTITY_ID_IN_EVENT,
    ENTITY_ID_LAST_UPDATED_INDEX,
    OLD_ENTITY_ID_IN_EVENT,
    Events,
    States,
)

from .common import (
    apply_states_filters,
    select_events_context_id_subquery,
    select_events_context_only,
    select_events_without_states,
    select_states,
    select_states_context_only,
)


def _select_entities_context_ids_sub_query(
    start_day: dt,
    end_day: dt,
    event_types: tuple[str, ...],
    entity_ids: list[str],
    json_quotable_entity_ids: list[str],
) -> CompoundSelect:
    """Generate a subquery to find context ids for multiple entities."""
    return select(
        union_all(
            select_events_context_id_subquery(start_day, end_day, event_types).where(
                apply_event_entity_id_matchers(json_quotable_entity_ids)
            ),
            apply_entities_hints(select(States.context_id))
            .filter((States.last_updated > start_day) & (States.last_updated < end_day))
            .where(States.entity_id.in_(entity_ids)),
        ).c.context_id
    )


def _apply_entities_context_union(
    query: Query,
    start_day: dt,
    end_day: dt,
    event_types: tuple[str, ...],
    entity_ids: list[str],
    json_quotable_entity_ids: list[str],
) -> CompoundSelect:
    """Generate a CTE to find the entity and device context ids and a query to find linked row."""
    entities_cte: CTE = _select_entities_context_ids_sub_query(
        start_day,
        end_day,
        event_types,
        entity_ids,
        json_quotable_entity_ids,
    ).cte()
    return query.union_all(
        states_query_for_entity_ids(start_day, end_day, entity_ids),
        select_events_context_only().where(
            Events.context_id.in_(entities_cte.select())
        ),
        select_states_context_only()
        .where(States.entity_id.not_in(entity_ids))
        .where(States.context_id.in_(entities_cte.select())),
    )


def entities_stmt(
    start_day: dt,
    end_day: dt,
    event_types: tuple[str, ...],
    entity_ids: list[str],
    json_quotable_entity_ids: list[str],
) -> StatementLambdaElement:
    """Generate a logbook query for multiple entities."""
    return lambda_stmt(
        lambda: _apply_entities_context_union(
            select_events_without_states(start_day, end_day, event_types).where(
                apply_event_entity_id_matchers(json_quotable_entity_ids)
            ),
            start_day,
            end_day,
            event_types,
            entity_ids,
            json_quotable_entity_ids,
        ).order_by(Events.time_fired)
    )


def states_query_for_entity_ids(
    start_day: dt, end_day: dt, entity_ids: list[str]
) -> Query:
    """Generate a select for states from the States table for specific entities."""
    return apply_states_filters(
        apply_entities_hints(select_states()), start_day, end_day
    ).where(States.entity_id.in_(entity_ids))


def apply_event_entity_id_matchers(
    json_quotable_entity_ids: Iterable[str],
) -> sqlalchemy.or_:
    """Create matchers for the entity_id in the event_data."""
    return ENTITY_ID_IN_EVENT.in_(
        json_quotable_entity_ids
    ) | OLD_ENTITY_ID_IN_EVENT.in_(json_quotable_entity_ids)


def apply_entities_hints(query: Query) -> Query:
    """Force mysql to use the right index on large selects."""
    return query.with_hint(
        States, f"FORCE INDEX ({ENTITY_ID_LAST_UPDATED_INDEX})", dialect_name="mysql"
    )
