from __future__ import annotations

from dataclasses import FrozenInstanceError
from typing import cast

import pytest

from gateway.platforms.telegram_nutrition_addresses import (
    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,
)


@pytest.mark.parametrize(
    "source",
    [
        {"user_id": "200", "chat_id": "200"},
        {"user_id": "200", "chat_id": "200", "topic_id": None},
        {"user_id": "200", "chat_id": "200", "topic_id": 0},
        {"user_id": "200", "chat_id": "200", "topic_id": "0"},
    ],
    ids=("absent", "null", "integer-zero", "persisted-zero"),
)
def test_customer_dm_normalizes_topic(source: dict[str, object]) -> None:
    address = parse_customer_private_delivery_address(source)

    assert address == CustomerPrivateDeliveryAddress(200, 200, 0)
    with pytest.raises(FrozenInstanceError):
        setattr(address, "chat_id", 201)


@pytest.mark.parametrize(
    "chat_id",
    ["-123", "-100123", "-100999"],
    ids=("group", "supergroup", "channel"),
)
def test_customer_delivery_rejects_non_private_chat(chat_id: str) -> None:
    with pytest.raises(AddressConfigurationError, match="private Telegram chat.*Bot DM"):
        _ = parse_customer_private_delivery_address(
            {"user_id": "200", "chat_id": chat_id, "topic_id": "0"}
        )


def test_customer_delivery_rejects_nonzero_topic() -> None:
    with pytest.raises(AddressConfigurationError, match="topic_id 0.*Bot DM"):
        _ = parse_customer_private_delivery_address(
            {"user_id": "200", "chat_id": "200", "topic_id": "1"}
        )


@pytest.mark.parametrize(
    ("role", "source"),
    [
        (
            StaffReviewRole.OWNER,
            {"user_id": "100", "chat_id": "-100123", "topic_id": "90"},
        ),
        (
            StaffReviewRole.OPERATOR,
            {"user_id": "101", "chat_id": "-100123", "topic_id": "91"},
        ),
    ],
)
def test_staff_addresses_preserve_trusted_role(
    role: StaffReviewRole,
    source: dict[str, object],
) -> None:
    address = parse_staff_review_address(role, source)

    assert address == StaffReviewAddress(
        role,
        int(cast(str, source["user_id"])),
        int(cast(str, source["chat_id"])),
        int(cast(str, source["topic_id"])),
    )
    with pytest.raises(FrozenInstanceError):
        setattr(address, "role", StaffReviewRole.OPERATOR)


@pytest.mark.parametrize(
    "source",
    [
        {"user_id": "bad", "chat_id": "200", "topic_id": "0"},
        {"user_id": "200", "chat_id": "", "topic_id": "0"},
        {"user_id": "200", "chat_id": "200", "topic_id": "-1"},
        {"user_id": True, "chat_id": "200", "topic_id": "0"},
        {"user_id": "200", "chat_id": False, "topic_id": "0"},
        {"user_id": "200", "chat_id": "200", "topic_id": True},
    ],
    ids=("bad-user", "bad-chat", "bad-topic", "bool-user", "bool-chat", "bool-topic"),
)
def test_customer_parser_rejects_malformed_and_bool_ids(
    source: dict[str, object],
) -> None:
    with pytest.raises(AddressConfigurationError):
        _ = parse_customer_private_delivery_address(source)


def test_staff_parser_rejects_bool_and_untrusted_role_metadata() -> None:
    with pytest.raises(AddressConfigurationError):
        _ = parse_staff_review_address(
            StaffReviewRole.OPERATOR,
            {"user_id": "101", "chat_id": "-100123", "topic_id": False},
        )

    confused = {
        "user_id": "201",
        "chat_id": "-100123",
        "topic_id": "71",
        "role": "operator",
    }
    with pytest.raises(AddressConfigurationError, match="unexpected fields.*role"):
        _ = parse_staff_review_address(StaffReviewRole.OWNER, confused)

    with pytest.raises(AddressConfigurationError, match="trusted staff review role"):
        _ = parse_staff_review_address(
            cast(StaffReviewRole, cast(object, "operator")),
            {"user_id": "201", "chat_id": "-100123", "topic_id": "71"},
        )


def test_legacy_shared_topic_error_names_required_migration() -> None:
    customer = {"user_id": "200", "chat_id": "-100123", "topic_id": "70"}
    staff = (
        {"user_id": "201", "chat_id": "-100123", "topic_id": "71"},
        {"user_id": "100", "chat_id": "-100123", "topic_id": "90"},
    )

    assert is_legacy_shared_topic_configuration(customer, staff)
    with pytest.raises(LegacySharedTopicConfigurationError) as raised:
        reject_legacy_shared_topic_configuration(customer, staff)
    assert "migrate customer delivery to the customer Bot DM" in str(raised.value)
    assert "remove the customer from the staff review group" in str(raised.value)


def test_private_customer_dm_is_not_legacy_shared_topic() -> None:
    customer = {"user_id": "200", "chat_id": "200", "topic_id": None}
    staff = ({"user_id": "201", "chat_id": "-100123", "topic_id": "71"},)

    assert not is_legacy_shared_topic_configuration(customer, staff)


def test_customer_membership_in_staff_group_blocks_readiness() -> None:
    customer = CustomerPrivateDeliveryAddress(200, 200, 0)
    staff = (
        StaffReviewAddress(StaffReviewRole.OPERATOR, 201, -100123, 71),
        StaffReviewAddress(StaffReviewRole.OWNER, 100, -100123, 90),
    )

    with pytest.raises(
        AddressReadinessError,
        match="customer 200.*staff review chat -100123.*remove the customer",
    ):
        reject_customer_staff_membership(
            customer,
            staff,
            customer_member_chat_ids=(-100123,),
        )

    reject_customer_staff_membership(
        customer,
        staff,
        customer_member_chat_ids=(-100999,),
    )
