#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.12"
# dependencies = []
# ///

# ─── How to run ───
# 1. Install uv: curl -LsSf https://astral.sh/uv/install.sh | sh
# 2. Use through: uv run python scripts/run_nutricoach_v140_golden_path.py --help
# ──────────────────

"""Durable deterministic Telegram provider with no network surface."""

from __future__ import annotations

import fcntl
import hashlib
import os

from datetime import datetime
from pathlib import Path
from typing import TypeVar, final, override

from telegram import Bot, Chat, Message
from telegram.error import TimedOut

from gateway.platforms.telegram import ReminderNoSendRejected
from scripts.nutricoach_v140_golden_path_faults import observe_provider
from scripts.nutricoach_v140_golden_path_models import ProviderCall, ProviderMode
from scripts.nutricoach_v140_golden_path_privacy import scan_provider_text

_Option = TypeVar("_Option")


@final
class DurableFakeTelegram(Bot):
    """Serialize provider calls before returning each deterministic outcome."""

    def __init__(self, path: Path, now: datetime, mode: ProviderMode) -> None:
        super().__init__("123456:GoldenPathNoNetworkToken")
        self._path = path
        self._now = now
        self._mode = mode
        path.parent.mkdir(parents=True, exist_ok=True)
        path.touch(mode=0o600)
        path.chmod(0o600)

    def _append(
        self, kind: str, chat_id: str, topic_id: str, text: str, outcome: str,
        message_id: str,
    ) -> ProviderCall:
        with self._path.open("r+", encoding="utf-8") as stream:
            fcntl.flock(stream.fileno(), fcntl.LOCK_EX)
            rows = [ProviderCall.model_validate_json(line) for line in stream if line.strip()]
            sequence = len(rows) + 1
            day = self._now.date().isoformat()
            logical_suffix = message_id if kind == "topic59_edit" else topic_id
            scan = scan_provider_text(kind, text)
            call = ProviderCall(
                sequence=sequence, call_key=f"{kind}:{day}:{logical_suffix}",
                kind=kind, kst_day=day, chat_id=chat_id, topic_id=topic_id,
                message_id=message_id, text_sha256=hashlib.sha256(text.encode()).hexdigest(),
                outcome=outcome, privacy_schema=scan.schema_name,
                privacy_scan_count=scan.scanned_categories,
                privacy_leak_categories=scan.leak_categories,
            )
            _ = stream.seek(0, os.SEEK_END)
            _ = stream.write(call.model_dump_json() + "\n")
            stream.flush()
            os.fsync(stream.fileno())
            return call

    @override
    async def send_message(
        self, chat_id: str | int, text: str, *args: _Option,
        message_thread_id: int | None = None, **kwargs: _Option,
    ) -> Message:
        _ = args, kwargs
        topic = "" if message_thread_id is None else str(message_thread_id)
        if topic == "59":
            kind = "topic59_send"
        elif text == "체크인이 확인되지 않았습니다. 오늘 아침 체크인을 제출해 주세요.":
            kind = "reminder"
        elif str(chat_id) == "owner-dm":
            kind = "owner_dm"
        else:
            kind = "generic_schedule"
        known = kind == "reminder" and self._mode is ProviderMode.KNOWN_FAILURE
        unknown = kind == "reminder" and self._mode is ProviderMode.UNKNOWN
        message_id = str(self._next_sequence())
        _ = self._append(
            kind, str(chat_id), topic, text,
            "known_failure" if known else "unknown" if unknown else "delivered",
            message_id,
        )
        observe_provider(f"send:{kind}")
        if known:
            raise ReminderNoSendRejected("injected_known_failure")
        if unknown:
            raise TimedOut("injected_provider_unknown")
        if self._mode is ProviderMode.CRASH and kind == "reminder":
            os._exit(86)
        return Message(
            message_id=int(message_id), date=self._now,
            chat=Chat(id=int(chat_id) if str(chat_id).isdigit() else 1, type="private"),
        )

    @override
    async def edit_message_text(
        self, text: str, chat_id: str | int | None = None,
        message_id: int | None = None, *args: _Option, **kwargs: _Option,
    ) -> Message | bool:
        _ = args, kwargs
        if chat_id is None or message_id is None:
            return False
        _ = await self.edit(str(chat_id), str(message_id), text)
        return True

    async def edit(self, chat_id: str, message_id: str, text: str) -> str:
        outcome = "unknown" if self._mode is ProviderMode.UNKNOWN else "delivered"
        _ = self._append("topic59_edit", chat_id, "59", text, outcome, message_id)
        observe_provider("edit:topic59")
        if self._mode is ProviderMode.UNKNOWN:
            raise TimedOut("injected_edit_unknown")
        if self._mode is ProviderMode.CRASH:
            os._exit(86)
        return message_id

    def _next_sequence(self) -> int:
        with self._path.open(encoding="utf-8") as stream:
            fcntl.flock(stream.fileno(), fcntl.LOCK_SH)
            return sum(1 for line in stream if line.strip()) + 1


def read_provider_calls(path: Path) -> tuple[ProviderCall, ...]:
    if not path.exists():
        return ()
    return tuple(
        ProviderCall.model_validate_json(line)
        for line in path.read_text(encoding="utf-8").splitlines()
        if line.strip()
    )
