"""Typed real-host support for weekly dispatcher tests."""

from __future__ import annotations

from collections.abc import Awaitable, Callable, Mapping
from dataclasses import dataclass
from datetime import date, datetime
from enum import Enum
from pathlib import Path
from typing import final, override

import checkin_cli
import pytest

from checkin_cli.customer_coaching import CustomerRegistry
from checkin_cli.weekly_operations_schedule_host_models_r4 import (
    ApprovedReminderScheduleEvidence,
    CustomerScheduleTask,
    TaskKind,
    WeeklyOperationsSchedulePolicy,
)
from checkin_cli.weekly_operations_grounding import GroundedWeeklyReviewSource
from gateway.config import PlatformConfig
from gateway.platforms.base import SendResult
from gateway.platforms.nutrition_weekly_operations_config import JsonValue
from gateway.platforms.nutrition_weekly_owner_contract import WeeklyLifecycleResult
from gateway.platforms.nutrition_weekly_owner_draft import WeeklyOwnerDraftService
from gateway.platforms.nutrition_weekly_owner_model import WeeklyExplanationModel
from gateway.platforms.nutrition_weekly_owner_request import (
    WeeklyOwnerDraftRequest,
    WeeklyOwnerDraftResult,
)
from gateway.platforms.nutrition_weekly_reminder_authority import (
    WeeklyReminderAuthorityOwner,
)
from gateway.platforms.nutrition_weekly_owner_storage import (
    WeeklyOwnerStorageAuthority,
    bind_weekly_owner_storage,
)
from gateway.platforms.telegram import ReminderNoSendRejected, TelegramAdapter
from tests.gateway._nutrition_weekly_reminder_support import (
    CANDIDATE,
    ReminderContextSource,
    ReminderOwnerFixture,
    reminder_owner_fixture,
)

BeforeSend = Callable[[], Awaitable[None]]


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


@dataclass(frozen=True, slots=True)
class ProviderCall:
    chat_id: str
    topic_id: str | int
    text: str
    has_reply_markup: bool


class ProviderMode(Enum):
    DELIVERED = "delivered"
    KNOWN = "known"
    UNKNOWN = "unknown"


@final
class FakeTelegramProvider:
    def __init__(
        self,
        mode: ProviderMode = ProviderMode.DELIVERED,
        before_send: BeforeSend | None = None,
    ) -> None:
        self.mode = mode
        self.before_send = before_send
        self.calls: list[ProviderCall] = []

    async def send_message(
        self,
        *,
        chat_id: str,
        text: str,
        message_thread_id: str | int,
        reply_markup: JsonValue | None = None,
        **_options: JsonValue,
    ) -> ProviderMessage:
        if self.before_send is not None:
            await self.before_send()
        self.calls.append(
            ProviderCall(chat_id, message_thread_id, text, reply_markup is not None)
        )
        if self.mode is ProviderMode.KNOWN:
            raise ReminderNoSendRejected("known_rejection")
        if self.mode is ProviderMode.UNKNOWN:
            raise TimeoutError("unknown")
        return ProviderMessage(len(self.calls))


from tests.gateway._nutrition_weekly_dispatcher_config_support import (
    weekly_platform_config,
)


@final
class WeeklyDispatcherCoordinator:
    def __init__(self, fixture: ReminderOwnerFixture, root: Path) -> None:
        self.fixture = fixture
        self.profile_root = root
        self.registry = fixture.registry
        self.storage = bind_weekly_owner_storage(root / "data" / "owner-actions")
        self.drafts: dict[str, WeeklyLifecycleResult] = {}
        self.owner_routes: list[tuple[str, str, str]] = []
        self.next_registry: CustomerRegistry | None = None
        self.owner_accesses = 0
        self.before_owner_access: tuple[int, Callable[[], None]] | None = None
        self.weekly_session: str | None = "weekly-session"

    def refresh_live_registry(self) -> bool:
        if self.next_registry is not None:
            self.registry, self.next_registry = self.next_registry, None
        return True

    def queue_registry(self, registry: CustomerRegistry) -> None:
        self.next_registry = registry

    def run_before_owner_access(
        self, access: int, callback: Callable[[], None]
    ) -> None:
        self.before_owner_access = (access, callback)

    @property
    def weekly_reminder_authority_owner(self) -> WeeklyReminderAuthorityOwner:
        self.owner_accesses += 1
        pending = self.before_owner_access
        if pending is not None and pending[0] == self.owner_accesses:
            self.before_owner_access = None
            pending[1]()
        self.fixture.owner.verify_registry(self.registry)
        return self.fixture.owner

    def weekly_owner_key(self) -> tuple[str, str, str]:
        owner = self.registry.owner
        return owner.user_id, owner.chat_id, owner.topic_id

    def weekly_session_id(self, customer_key: str) -> str | None:
        assert customer_key == self.fixture.runtime.spec.customer_key
        return self.weekly_session

    def weekly_owner_storage_authority(self) -> WeeklyOwnerStorageAuthority:
        return self.storage

    def persist_grounded_weekly_review(
        self,
        customer_key: str,
        owner_key: tuple[str, str, str],
        source: GroundedWeeklyReviewSource,
    ) -> WeeklyLifecycleResult:
        assert customer_key == self.fixture.runtime.spec.customer_key
        assert owner_key == self.weekly_owner_key() and source.verify()
        self.owner_routes.append(owner_key)
        result = WeeklyLifecycleResult(True, "weekly-1", source.render_customer_body())
        self.drafts["weekly-1"] = result
        return result

    def grounded_weekly_review_result(
        self, draft_id: str, owner_key: tuple[str, str, str]
    ) -> WeeklyLifecycleResult:
        assert owner_key == self.weekly_owner_key()
        return self.drafts.get(draft_id, WeeklyLifecycleResult(False, error="missing"))

    def create_grounded_weekly_owner_draft(
        self, request: WeeklyOwnerDraftRequest, model: WeeklyExplanationModel
    ) -> WeeklyOwnerDraftResult:
        if self.weekly_session is None:
            return WeeklyOwnerDraftResult(
                False,
                None,
                0,
                False,
                "weekly_review_snapshot_unavailable",
            )
        return WeeklyOwnerDraftService(self, model).create(request)


@final
class AuthorizedTelegramHost(TelegramAdapter):
    _test_coordinator: WeeklyDispatcherCoordinator | None = None

    @classmethod
    def create(
        cls, coordinator: WeeklyDispatcherCoordinator,
        provider: FakeTelegramProvider, config: PlatformConfig,
    ) -> AuthorizedTelegramHost:
        instance = cls.__new__(cls)
        instance._test_coordinator = coordinator
        instance._bot = provider
        instance.config = config
        instance._weekly_platform_config = config
        instance._task26_candidate_digest = CANDIDATE
        instance._task26_service_revoked = False
        return instance

    @override
    def _get_nutrition_coaching(self) -> WeeklyDispatcherCoordinator:
        coordinator = self._test_coordinator
        if coordinator is None:
            raise AssertionError("test coordinator is unavailable")
        return coordinator

    async def run_authorized(self, now: datetime) -> SendResult:
        return await self._send_nutrition_coaching_tick_authorized(now)

    def replace_config(self, config: PlatformConfig) -> None:
        self.config = config
        self._weekly_platform_config = config

    def replace_candidate(self, digest: str) -> None:
        self._task26_candidate_digest = digest


@dataclass(frozen=True, slots=True)
class DispatcherFixture:
    root: Path
    reminder: ReminderOwnerFixture
    coordinator: WeeklyDispatcherCoordinator
    host: AuthorizedTelegramHost
    provider: FakeTelegramProvider
    task: CustomerScheduleTask

    def install_due(self, monkeypatch: pytest.MonkeyPatch) -> None:
        tasks = (self.task,)

        def due(
            _registry: CustomerRegistry, _now: datetime, *,
            missing_morning_checkins: Mapping[str, date] | None = None,
            reminder_evidence: ApprovedReminderScheduleEvidence | None = None,
            weekly_operations_policy: WeeklyOperationsSchedulePolicy | None = None,
        ) -> tuple[CustomerScheduleTask, ...]:
            _ = (missing_morning_checkins, reminder_evidence, weekly_operations_policy)
            return tasks

        monkeypatch.setattr(checkin_cli, "build_due_customer_tasks", due)
        fence = checkin_cli.initialize_schedule_delivery_fence(self.root)
        assert fence.state == "ready"


def dispatcher_fixture(
    root: Path, kind: TaskKind,
    context: ReminderContextSource | None = None,
    provider: FakeTelegramProvider | None = None,
) -> DispatcherFixture:
    reminder = reminder_owner_fixture(root, context, topic_id="41")
    coordinator = WeeklyDispatcherCoordinator(reminder, root)
    transport = provider or FakeTelegramProvider()
    host = AuthorizedTelegramHost.create(
        coordinator, transport, weekly_platform_config(reminder)
    )
    task = CustomerScheduleTask(
        reminder.runtime.spec.customer_key, kind, date(2026, 8, 17)
    )
    return DispatcherFixture(root, reminder, coordinator, host, transport, task)
