from __future__ import annotations

import asyncio
import logging
from collections.abc import Awaitable, Callable
from datetime import date

from checkin_cli.nutrition_onboarding_contract import (
    QUESTION_FIELDS,
    example_answer,
)

from gateway.platforms.nutrition_onboarding_reconciliation import (
    NutritionOnboardingReconciler,
)
from gateway.platforms.telegram_nutrition_onboarding_copy import (
    PROMPTS,
)
from gateway.platforms.telegram_nutrition_onboarding_preview_callback import (
    TelegramNutritionOnboardingPreviewCallbackMixin,
)
from gateway.platforms.telegram_nutrition_onboarding_preview_types import (
    PreviewRoute,
    PreviewSession,
    route_key as _route_key,
)
from gateway.platforms.telegram_nutrition_onboarding_preview_flow import (
    TelegramNutritionOnboardingPreviewFlowMixin,
)


PREVIEW_COMMAND = "/nutritionpreview"
PREVIEW_NAMESPACE = "[PREVIEW][NUTRITION ONBOARDING]"
PREVIEW_TOPIC_NAME = "[PREVIEW] 영양 온보딩"
logger = logging.getLogger(__name__)


class TelegramNutritionOnboardingPreview(
    TelegramNutritionOnboardingPreviewCallbackMixin,
    TelegramNutritionOnboardingPreviewFlowMixin
):
    def __init__(
        self,
        route_supplier: Callable[[], PreviewRoute | None],
        sender: Callable[
            [PreviewRoute, str, bool, str | None, str | None],
            Awaitable[object],
        ],
        *,
        expires_seconds: float = 3600,
        reconciler: NutritionOnboardingReconciler | None = None,
    ) -> None:
        self._route_supplier = route_supplier
        self._sender = sender
        self._expires_seconds = expires_seconds
        self._reconciler = reconciler or NutritionOnboardingReconciler()
        self._session: PreviewSession | None = None
        self._expiry: asyncio.TimerHandle | None = None
        self._lock = asyncio.Lock()
        self._consumed_message_ids: set[str] = set()

    def reserves(self, message: object) -> bool:
        route = self._route_supplier()
        return route is not None and _route_key(message)[1:] == (
            route.chat_id,
            route.topic_id,
        )

    async def handle_command(self, message: object) -> bool:
        if not self.reserves(message):
            return False
        route = self._route_supplier()
        if route is None or _route_key(message) != (
            route.user_id,
            route.chat_id,
            route.topic_id,
        ):
            return True
        text = " ".join(str(getattr(message, "text", "")).split())
        parts = text.split(maxsplit=1)
        argument = parts[1].lower() if len(parts) == 2 else ""
        async with self._lock:
            if argument == "stop":
                self.purge()
                await self._send_notice(
                    message,
                    route,
                    "미리보기를 종료했습니다. 입력값은 저장되지 않았습니다.",
                )
                return True
            if argument == "qa":
                self.purge()
                answers = tuple(
                    (
                        field,
                        (
                            "점심 또는 저녁 중 하나만 가능합니다"
                            if field == "schedule_constraints"
                            else example_answer(field)
                        ),
                    )
                    for field in QUESTION_FIELDS
                )
                await self._finish_answers(
                    message,
                    route,
                    answers,
                    allow_clarifications=True,
                    reference_date=date.today(),
                )
                return True
            if argument not in {"", "reset"}:
                await self._send_notice(
                    message,
                    route,
                    "사용법: /nutritionpreview [reset|qa|stop]",
                )
                return True
            cursor = 0 if argument == "reset" or self._session is None else (
                self._session.cursor
            )
            answers = (
                ()
                if argument == "reset" or self._session is None
                else self._session.answers
            )
            if argument == "reset":
                self.purge()
            await self._publish_question(
                message,
                route,
                cursor,
                answers,
            )
        return True

    def purge(self) -> None:
        self._session = None
        self._consumed_message_ids.clear()
        if self._expiry is not None:
            self._expiry.cancel()
            self._expiry = None

    def close(self) -> None:
        self.purge()

    async def _publish_question(
        self,
        message: object,
        route: PreviewRoute,
        cursor: int,
        answers: tuple[tuple[str, object], ...],
    ) -> None:
        field = QUESTION_FIELDS[cursor]
        text = (
            f"{PREVIEW_NAMESPACE}\n"
            f"[{cursor + 1}/{len(QUESTION_FIELDS)}] {PROMPTS[field]}\n"
            "이 메시지에 답장해 주세요."
        )
        trigger_id = str(getattr(message, "message_id", "") or "")
        if not trigger_id:
            self.purge()
            return
        try:
            receipt = await self._sender(
                route,
                text,
                True,
                trigger_id,
                None,
            )
        except Exception as exc:
            self.purge()
            logger.warning("Nutrition onboarding preview publication failed: %s", exc)
            return
        receipt_id = str(getattr(receipt, "message_id", "") or "")
        if not receipt_id:
            self.purge()
            return
        reference_date = (
            self._session.reference_date
            if self._session is not None
            else date.today()
        )
        self._session = PreviewSession(
            cursor,
            receipt_id,
            answers,
            reference_date,
        )
        log_receipt = logger.warning if cursor == 0 else logger.info
        log_receipt(
            "Nutrition onboarding preview published: chat=%s topic=%s "
            "message_id=%s field=%s",
            route.chat_id,
            route.topic_id,
            receipt_id,
            field,
        )
        self._arm_expiry()

    async def _send_notice(
        self,
        message: object,
        route: PreviewRoute,
        text: str,
    ) -> object | None:
        try:
            return await self._sender(
                route,
                f"{PREVIEW_NAMESPACE}\n{text}",
                False,
                None,
                None,
            )
        except Exception:
            self.purge()
            logger.warning(
                "Nutrition onboarding preview notice publication failed",
                exc_info=True,
            )
            return None

    def _arm_expiry(self) -> None:
        if self._expiry is not None:
            self._expiry.cancel()
        self._expiry = asyncio.get_running_loop().call_later(
            self._expires_seconds,
            self.purge,
        )
