"""TelegramAdapter send/edit QA with an ordinary network-free fake provider."""

from __future__ import annotations

import hashlib
from dataclasses import dataclass
from datetime import date, datetime
from pathlib import Path
from typing import Protocol, final
from zoneinfo import ZoneInfo

import pytest

from gateway.platforms.nutrition_weekly_operations import (
    Topic59DayCardProjector,
    Topic59PublicationDisposition,
    Topic59PublicationRequest,
)
from tests.gateway._weekly_operations_authority_support import (
    authority_receipt_fixture,
    test_registry_identity as registry_identity,
)
from gateway.platforms.nutrition_weekly_operations_authority import (
    WeeklyOperationsAuthorityReceipt,
    WeeklyOperationsOwner,
    WeeklyOperationsRuntimeContext,
)
from gateway.platforms.nutrition_weekly_operations_config import WeeklyOperationsConfig, WeeklyOperationsReviewRoute
from gateway.platforms.nutrition_weekly_operations_models import (
    Topic59ApprovalState,
    Topic59DayCard,
    Topic59DayState,
    Topic59DeliveryState,
    Topic59DraftState,
)
from gateway.platforms.nutrition_weekly_operations_publication_contract import (
    Topic59ProviderDelivered,
    Topic59ProviderFailure,
    Topic59ProviderOutcome,
    Topic59ProviderUnknown,
)
from gateway.platforms.telegram import TelegramAdapter


_KST = ZoneInfo("Asia/Seoul")
_KEY = "client_001"
_IDENTITY = hashlib.sha256(f"nutricoach-weekly-operations-customer-identity-v1\0{_KEY}".encode()).hexdigest()
_CANDIDATE = hashlib.sha256(b"candidate").hexdigest()
_CONSENT = hashlib.sha256(b"consent").hexdigest()


@pytest.fixture
def anyio_backend() -> str:
    return "asyncio"


class Topic59FakeTransportError(Exception):
    """The focused fake provider was called outside its exact Topic-59 seam."""


@dataclass(frozen=True, slots=True)
class _Message:
    message_id: int


class _FakeTelegramBot:
    def __init__(self) -> None:
        self.sent: list[dict[str, str | int]] = []
        self.edited: list[dict[str, str | int]] = []

    async def send_message(self, **kwargs: str | int) -> _Message:
        self.sent.append(kwargs)
        return _Message(101)

    async def edit_message_text(self, **kwargs: str | int) -> None:
        self.edited.append(kwargs)


@dataclass(frozen=True, slots=True)
class _SidecarRow:
    customer_identity_digest: str
    kst_day: date
    state: str
    source_event_id: str | None
    logical_key: str
    row_digest: str


class _EditReceipt(Protocol):
    success: bool
    message_id: str | None


class _TelegramTopicSeam(Protocol):
    async def send_topic(self, **kwargs: str | int) -> _Message: ...

    async def edit_message(
        self, chat_id: str, message_id: str, content: str, *, finalize: bool = False
    ) -> _EditReceipt: ...


@final
class _AdapterHarness(TelegramAdapter):
    _bot: _FakeTelegramBot

    def attach_bot(self, bot: _FakeTelegramBot) -> None:
        self._bot = bot

    async def send_topic(self, **kwargs: str | int) -> _Message:
        if "message_thread_id" not in kwargs:
            raise Topic59FakeTransportError("missing strict topic")
        return await self._bot.send_message(**kwargs)


class _AdapterTransport:
    def __init__(self, adapter: _TelegramTopicSeam) -> None:
        self._adapter: _TelegramTopicSeam = adapter

    async def send(self, *, chat_id: str, topic_id: str, text: str) -> Topic59ProviderOutcome:
        if topic_id != "59":
            return Topic59ProviderUnknown(Topic59ProviderFailure.REJECTED)
        receipt = await self._adapter.send_topic(chat_id=chat_id, message_thread_id=topic_id, text=text)
        return Topic59ProviderDelivered(str(receipt.message_id))

    async def edit(self, *, chat_id: str, topic_id: str, message_id: str, text: str) -> Topic59ProviderOutcome:
        if topic_id != "59":
            return Topic59ProviderUnknown(Topic59ProviderFailure.REJECTED)
        receipt = await self._adapter.edit_message(chat_id, message_id, text)
        if not receipt.success or receipt.message_id is None:
            return Topic59ProviderUnknown(Topic59ProviderFailure.CONNECTION_LOST)
        return Topic59ProviderDelivered(str(receipt.message_id))


def _digest(label: str) -> str:
    return hashlib.sha256(label.encode()).hexdigest()


def _request(state: Topic59DayState) -> Topic59PublicationRequest:
    config = WeeklyOperationsConfig(
        True,
        review_route=WeeklyOperationsReviewRoute("90001", "10001"),
        registry_identity_binding_digest=registry_identity().binding_digest,
    )
    receipt = authority_receipt_fixture(
        _CANDIDATE, config.digest, (_KEY,), ("90001", "10001", 1),
        _CONSENT, datetime(2026, 8, 1, tzinfo=_KST), datetime(2026, 9, 1, tzinfo=_KST), config.feature_epoch,
    )
    runtime = WeeklyOperationsRuntimeContext(
        _CANDIDATE, _KEY, "90001", "10001", 1, _CONSENT, True, config.feature_epoch,
        datetime(2026, 8, 20, tzinfo=_KST),
    )
    card = Topic59DayCard.from_sidecar(
        _SidecarRow(_IDENTITY, date(2026, 8, 20), state.value, f"event-{state.value}", _digest(state.value), _digest(f"digest-{state.value}")),
        completed_field_count=5, draft_state=Topic59DraftState.DRAFTED,
        approval_state=Topic59ApprovalState.PENDING, delivery_state=Topic59DeliveryState.NOT_SENT,
        candidate_digest=_CANDIDATE,
    )
    return Topic59PublicationRequest(card, config, receipt, runtime)


@pytest.mark.anyio
async def test_real_adapter_uses_strict_topic59_send_then_edits_the_persisted_message(tmp_path: Path) -> None:
    bot = _FakeTelegramBot()
    adapter = _AdapterHarness.__new__(_AdapterHarness)
    adapter.attach_bot(bot)
    projector = Topic59DayCardProjector(tmp_path / "topic59.jsonl")
    transport = _AdapterTransport(adapter)

    first = await projector.publish(_request(Topic59DayState.MISSED), transport)
    later = await projector.publish(_request(Topic59DayState.LATE_SUBMITTED), transport)

    sent_text = bot.sent[0]["text"]
    edited_text = bot.edited[0]["text"]
    assert first.disposition is Topic59PublicationDisposition.SENT
    assert later.disposition is Topic59PublicationDisposition.EDITED
    assert isinstance(sent_text, str) and isinstance(edited_text, str)
    assert set(bot.sent[0]) == {"chat_id", "message_thread_id", "text"}
    assert set(bot.edited[0]) == {"chat_id", "message_id", "text"}
    assert bot.edited[0]["chat_id"] == 10001 and bot.edited[0]["message_id"] == 101
    assert "지연 제출" in edited_text
    assert all(value not in sent_text for value in {"홍길동", "70.3", "kcal", "증상"})
