"""Sensor platform for Ghost."""

from collections.abc import Callable
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, override

from homeassistant.components.sensor import (
    SensorDeviceClass,
    SensorEntity,
    SensorEntityDescription,
    SensorStateClass,
)
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity

from .const import CURRENCY, DOMAIN, MANUFACTURER, MODEL
from .coordinator import GhostData, GhostDataUpdateCoordinator

if TYPE_CHECKING:
    from . import GhostConfigEntry

# Coordinator handles batching, no limit needed.
PARALLEL_UPDATES = 0


def _get_currency_value(currency_data: dict[str, Any]) -> int | None:
    """Extract the first currency value from a currency dict."""
    if not currency_data:
        return None
    first_value = next(iter(currency_data.values()), None)
    if first_value is None:
        return None
    return int(first_value)


@dataclass(frozen=True, kw_only=True)
class GhostSensorEntityDescription(SensorEntityDescription):
    """Describes a Ghost sensor entity."""

    value_fn: Callable[[GhostData], str | int | None]


SENSORS: tuple[GhostSensorEntityDescription, ...] = (
    # Core member metrics
    GhostSensorEntityDescription(
        key="total_members",
        translation_key="total_members",
        state_class=SensorStateClass.TOTAL,
        value_fn=lambda data: data.members.get("total", 0),
    ),
    GhostSensorEntityDescription(
        key="paid_members",
        translation_key="paid_members",
        state_class=SensorStateClass.TOTAL,
        value_fn=lambda data: data.members.get("paid", 0),
    ),
    GhostSensorEntityDescription(
        key="free_members",
        translation_key="free_members",
        state_class=SensorStateClass.TOTAL,
        value_fn=lambda data: data.members.get("free", 0),
    ),
    GhostSensorEntityDescription(
        key="comped_members",
        translation_key="comped_members",
        state_class=SensorStateClass.TOTAL,
        value_fn=lambda data: data.members.get("comped", 0),
    ),
    GhostSensorEntityDescription(
        key="gift_members",
        translation_key="gift_members",
        state_class=SensorStateClass.TOTAL,
        value_fn=lambda data: data.members.get("gift", 0),
    ),
    # Post metrics
    GhostSensorEntityDescription(
        key="published_posts",
        translation_key="published_posts",
        state_class=SensorStateClass.TOTAL,
        value_fn=lambda data: data.posts.get("published", 0),
    ),
    GhostSensorEntityDescription(
        key="draft_posts",
        translation_key="draft_posts",
        state_class=SensorStateClass.TOTAL,
        value_fn=lambda data: data.posts.get("drafts", 0),
    ),
    GhostSensorEntityDescription(
        key="scheduled_posts",
        translation_key="scheduled_posts",
        state_class=SensorStateClass.TOTAL,
        value_fn=lambda data: data.posts.get("scheduled", 0),
    ),
    GhostSensorEntityDescription(
        key="latest_post",
        translation_key="latest_post",
        value_fn=lambda data: (
            data.latest_post.get("title") if data.latest_post else None
        ),
    ),
    # Email metrics
    GhostSensorEntityDescription(
        key="latest_email",
        translation_key="latest_email",
        value_fn=lambda data: (
            data.latest_email.get("title") if data.latest_email else None
        ),
    ),
    GhostSensorEntityDescription(
        key="latest_email_sent",
        translation_key="latest_email_sent",
        state_class=SensorStateClass.TOTAL,
        value_fn=lambda data: (
            data.latest_email.get("email_count") if data.latest_email else None
        ),
    ),
    GhostSensorEntityDescription(
        key="latest_email_opened",
        translation_key="latest_email_opened",
        state_class=SensorStateClass.TOTAL,
        value_fn=lambda data: (
            data.latest_email.get("opened_count") if data.latest_email else None
        ),
    ),
    GhostSensorEntityDescription(
        key="latest_email_open_rate",
        translation_key="latest_email_open_rate",
        native_unit_of_measurement="%",
        value_fn=lambda data: (
            data.latest_email.get("open_rate") if data.latest_email else None
        ),
    ),
    GhostSensorEntityDescription(
        key="latest_email_clicked",
        translation_key="latest_email_clicked",
        state_class=SensorStateClass.TOTAL,
        value_fn=lambda data: (
            data.latest_email.get("clicked_count") if data.latest_email else None
        ),
    ),
    GhostSensorEntityDescription(
        key="latest_email_click_rate",
        translation_key="latest_email_click_rate",
        native_unit_of_measurement="%",
        value_fn=lambda data: (
            data.latest_email.get("click_rate") if data.latest_email else None
        ),
    ),
    # Social/ActivityPub metrics
    GhostSensorEntityDescription(
        key="socialweb_followers",
        translation_key="socialweb_followers",
        state_class=SensorStateClass.TOTAL,
        value_fn=lambda data: data.activitypub.get("followers", 0),
    ),
    GhostSensorEntityDescription(
        key="socialweb_following",
        translation_key="socialweb_following",
        state_class=SensorStateClass.TOTAL,
        value_fn=lambda data: data.activitypub.get("following", 0),
    ),
    # Engagement metrics
    GhostSensorEntityDescription(
        key="total_comments",
        translation_key="total_comments",
        state_class=SensorStateClass.TOTAL,
        value_fn=lambda data: data.comments,
    ),
)


REVENUE_SENSORS: tuple[GhostSensorEntityDescription, ...] = (
    GhostSensorEntityDescription(
        key="mrr",
        translation_key="mrr",
        device_class=SensorDeviceClass.MONETARY,
        state_class=SensorStateClass.TOTAL,
        native_unit_of_measurement=CURRENCY,
        suggested_display_precision=0,
        value_fn=lambda data: _get_currency_value(data.mrr),
    ),
    GhostSensorEntityDescription(
        key="arr",
        translation_key="arr",
        device_class=SensorDeviceClass.MONETARY,
        state_class=SensorStateClass.TOTAL,
        native_unit_of_measurement=CURRENCY,
        suggested_display_precision=0,
        value_fn=lambda data: _get_currency_value(data.arr),
    ),
)


async def async_setup_entry(
    hass: HomeAssistant,
    entry: GhostConfigEntry,
    async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
    """Set up Ghost sensors based on a config entry."""
    coordinator = entry.runtime_data.coordinator

    entities: list[GhostSensorEntity | GhostNewsletterSensorEntity] = [
        GhostSensorEntity(coordinator, description, entry) for description in SENSORS
    ]

    # Add revenue sensors only when Stripe is linked.
    if coordinator.data.mrr:
        entities.extend(
            GhostSensorEntity(coordinator, description, entry)
            for description in REVENUE_SENSORS
        )

    async_add_entities(entities)

    # Remove stale newsletter entities left over from previous runs.
    entity_registry = er.async_get(hass)
    prefix = f"{entry.unique_id}_newsletter_"
    active_newsletters = {
        newsletter_id
        for newsletter_id, newsletter in coordinator.data.newsletters.items()
        if newsletter.get("status") == "active"
    }
    for entity_entry in er.async_entries_for_config_entry(
        entity_registry, entry.entry_id
    ):
        if (
            entity_entry.unique_id.startswith(prefix)
            and entity_entry.unique_id[len(prefix) :] not in active_newsletters
        ):
            entity_registry.async_remove(entity_entry.entity_id)

    newsletter_added: set[str] = set()

    @callback
    def _async_update_newsletter_entities() -> None:
        """Add new and remove stale newsletter entities."""
        nonlocal newsletter_added

        active_newsletters = {
            newsletter_id
            for newsletter_id, newsletter in coordinator.data.newsletters.items()
            if newsletter.get("status") == "active"
        }

        new_newsletters = active_newsletters - newsletter_added

        if new_newsletters:
            async_add_entities(
                GhostNewsletterSensorEntity(
                    coordinator,
                    entry,
                    newsletter_id,
                    coordinator.data.newsletters[newsletter_id].get(
                        "name", "Newsletter"
                    ),
                )
                for newsletter_id in new_newsletters
            )
            newsletter_added.update(new_newsletters)

        removed_newsletters = newsletter_added - active_newsletters
        if removed_newsletters:
            entity_registry = er.async_get(hass)
            for newsletter_id in removed_newsletters:
                unique_id = f"{entry.unique_id}_newsletter_{newsletter_id}"
                entity_id = entity_registry.async_get_entity_id(
                    Platform.SENSOR, DOMAIN, unique_id
                )
                if entity_id:
                    entity_registry.async_remove(entity_id)
            newsletter_added -= removed_newsletters

    _async_update_newsletter_entities()
    entry.async_on_unload(
        coordinator.async_add_listener(_async_update_newsletter_entities)
    )


class GhostSensorEntity(CoordinatorEntity[GhostDataUpdateCoordinator], SensorEntity):
    """Representation of a Ghost sensor."""

    entity_description: GhostSensorEntityDescription
    _attr_has_entity_name = True

    def __init__(
        self,
        coordinator: GhostDataUpdateCoordinator,
        description: GhostSensorEntityDescription,
        entry: GhostConfigEntry,
    ) -> None:
        """Initialize the sensor."""
        super().__init__(coordinator)
        self.entity_description = description
        self._attr_unique_id = f"{entry.unique_id}_{description.key}"
        self._attr_device_info = DeviceInfo(
            entry_type=DeviceEntryType.SERVICE,
            identifiers={(DOMAIN, entry.entry_id)},
            manufacturer=MANUFACTURER,
            model=MODEL,
            configuration_url=coordinator.api.api_url,
        )

    @property
    @override
    def native_value(self) -> str | int | None:
        """Return the state of the sensor."""
        return self.entity_description.value_fn(self.coordinator.data)


class GhostNewsletterSensorEntity(
    CoordinatorEntity[GhostDataUpdateCoordinator], SensorEntity
):
    """Representation of a Ghost newsletter subscriber sensor."""

    _attr_has_entity_name = True
    _attr_translation_key = "newsletter_subscribers"
    _attr_state_class = SensorStateClass.TOTAL

    def __init__(
        self,
        coordinator: GhostDataUpdateCoordinator,
        entry: GhostConfigEntry,
        newsletter_id: str,
        newsletter_name: str,
    ) -> None:
        """Initialize the newsletter sensor."""
        super().__init__(coordinator)
        self._newsletter_id = newsletter_id
        self._newsletter_name = newsletter_name
        self._attr_unique_id = f"{entry.unique_id}_newsletter_{newsletter_id}"
        self._attr_translation_placeholders = {"newsletter_name": newsletter_name}
        self._attr_device_info = DeviceInfo(
            entry_type=DeviceEntryType.SERVICE,
            identifiers={(DOMAIN, entry.entry_id)},
            manufacturer=MANUFACTURER,
            model=MODEL,
            configuration_url=coordinator.api.api_url,
        )

    def _get_newsletter_by_id(self) -> dict[str, Any] | None:
        """Get newsletter data by ID."""
        return self.coordinator.data.newsletters.get(self._newsletter_id)

    @property
    @override
    def available(self) -> bool:
        """Return True if the entity is available."""
        return (
            super().available
            and self._newsletter_id in self.coordinator.data.newsletters
        )

    @property
    @override
    def native_value(self) -> int | None:
        """Return the subscriber count for this newsletter."""
        if newsletter := self._get_newsletter_by_id():
            count: int = newsletter.get("count", {}).get("members", 0)
            return count
        return None
