"""Typed Telegram addresses for DualCoach customer and staff routing."""

from __future__ import annotations

from collections.abc import Iterable, Mapping
from dataclasses import dataclass
from enum import StrEnum


class AddressConfigurationError(ValueError):
    """Raised when persisted Telegram address configuration is invalid."""


class LegacySharedTopicConfigurationError(AddressConfigurationError):
    """Raised when legacy shared-topic routing requires migration."""


class AddressReadinessError(AddressConfigurationError):
    """Raised when valid addresses are not safe for production activation."""


class StaffReviewRole(StrEnum):
    """Trusted authority roles for staff review destinations."""

    OWNER = "owner"
    OPERATOR = "operator"


@dataclass(frozen=True, slots=True)
class CustomerPrivateDeliveryAddress:
    """Canonical private Telegram destination for one customer."""

    user_id: int
    chat_id: int
    topic_id: int = 0


@dataclass(frozen=True, slots=True)
class StaffReviewAddress:
    """Typed Telegram destination for one trusted staff role."""

    role: StaffReviewRole
    user_id: int
    chat_id: int
    topic_id: int


def _parse_integer(
    value: object,
    *,
    field: str,
    minimum: int | None = None,
) -> int:
    if isinstance(value, bool):
        raise AddressConfigurationError(f"{field} must be an integer, not bool")
    if isinstance(value, int):
        parsed = value
    elif isinstance(value, str) and value.strip():
        try:
            parsed = int(value)
        except ValueError as exc:
            raise AddressConfigurationError(f"{field} must be an integer") from exc
    else:
        raise AddressConfigurationError(f"{field} must be an integer")
    if minimum is not None and parsed < minimum:
        raise AddressConfigurationError(f"{field} must be >= {minimum}")
    return parsed


def _reject_unexpected_fields(
    source: Mapping[str, object],
    *,
    allowed: frozenset[str],
) -> None:
    unexpected = sorted(set(source) - allowed)
    if unexpected:
        raise AddressConfigurationError(
            f"unexpected fields in Telegram address: {', '.join(unexpected)}"
        )


def parse_customer_private_delivery_address(
    source: Mapping[str, object],
) -> CustomerPrivateDeliveryAddress:
    """Parse a production customer destination at the configuration boundary."""

    _reject_unexpected_fields(
        source,
        allowed=frozenset({"user_id", "chat_id", "topic_id"}),
    )
    user_id = _parse_integer(source.get("user_id"), field="user_id", minimum=1)
    chat_id = _parse_integer(source.get("chat_id"), field="chat_id")
    if chat_id <= 0:
        raise AddressConfigurationError(
            "customer delivery requires a private Telegram chat; configure the customer Bot DM"
        )

    raw_topic = source.get("topic_id")
    topic_id = (
        0
        if raw_topic is None
        else _parse_integer(raw_topic, field="topic_id", minimum=0)
    )
    if topic_id != 0:
        raise AddressConfigurationError(
            "customer delivery requires canonical topic_id 0; configure the customer Bot DM"
        )
    return CustomerPrivateDeliveryAddress(
        user_id=user_id,
        chat_id=chat_id,
        topic_id=0,
    )


def parse_staff_review_address(
    role: object,
    source: Mapping[str, object],
) -> StaffReviewAddress:
    """Parse a staff destination while preserving trusted role authority."""

    if not isinstance(role, StaffReviewRole):
        raise AddressConfigurationError(
            "staff address requires a trusted staff review role"
        )
    _reject_unexpected_fields(
        source,
        allowed=frozenset({"user_id", "chat_id", "topic_id"}),
    )
    user_id = _parse_integer(source.get("user_id"), field="user_id", minimum=1)
    chat_id = _parse_integer(source.get("chat_id"), field="chat_id")
    if chat_id == 0:
        raise AddressConfigurationError(
            "staff review address requires a nonzero Telegram chat_id"
        )
    topic_id = _parse_integer(
        source.get("topic_id", 0),
        field="topic_id",
        minimum=0,
    )
    return StaffReviewAddress(
        role=role,
        user_id=user_id,
        chat_id=chat_id,
        topic_id=topic_id,
    )


def is_legacy_shared_topic_configuration(
    customer: Mapping[str, object],
    staff_addresses: Iterable[Mapping[str, object]],
) -> bool:
    """Return whether customer delivery shares a staff group through topics."""

    customer_chat_id = _parse_integer(customer.get("chat_id"), field="chat_id")
    raw_topic = customer.get("topic_id")
    customer_topic_id = (
        0
        if raw_topic is None
        else _parse_integer(raw_topic, field="topic_id", minimum=0)
    )
    if customer_chat_id >= 0 or customer_topic_id == 0:
        return False

    for staff in staff_addresses:
        staff_chat_id = _parse_integer(staff.get("chat_id"), field="chat_id")
        if staff_chat_id == customer_chat_id:
            return True
    return False


def reject_legacy_shared_topic_configuration(
    customer: Mapping[str, object],
    staff_addresses: Iterable[Mapping[str, object]],
) -> None:
    """Fail readiness with the exact safe migration required."""

    if is_legacy_shared_topic_configuration(customer, staff_addresses):
        raise LegacySharedTopicConfigurationError(
            " ".join(
                (
                    "legacy shared-topic routing is not production-ready:",
                    "migrate customer delivery to the customer Bot DM and",
                    "remove the customer from the staff review group",
                )
            )
        )


def reject_customer_staff_membership(
    customer: CustomerPrivateDeliveryAddress,
    staff_addresses: Iterable[StaffReviewAddress],
    *,
    customer_member_chat_ids: Iterable[int],
) -> None:
    """Reject activation while the customer remains in any staff review chat."""

    member_chat_ids = {
        _parse_integer(chat_id, field="customer_member_chat_id")
        for chat_id in customer_member_chat_ids
    }
    for staff in staff_addresses:
        if staff.chat_id in member_chat_ids:
            raise AddressReadinessError(
                " ".join(
                    (
                        f"customer {customer.user_id} is present in",
                        f"staff review chat {staff.chat_id};",
                        "remove the customer from the staff review group",
                        "before production activation",
                    )
                )
            )


__all__ = [
    "AddressConfigurationError",
    "AddressReadinessError",
    "CustomerPrivateDeliveryAddress",
    "LegacySharedTopicConfigurationError",
    "StaffReviewAddress",
    "StaffReviewRole",
    "is_legacy_shared_topic_configuration",
    "parse_customer_private_delivery_address",
    "parse_staff_review_address",
    "reject_customer_staff_membership",
    "reject_legacy_shared_topic_configuration",
]
