#!/usr/bin/env python3
"""Profile-explicit, non-consuming Telegram getMe readiness gate."""

from __future__ import annotations

import argparse
import asyncio
import hashlib
import importlib
import json
import os
import stat
import sys
from collections.abc import Callable
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Final

SUCCESSOR: Final = "e788f5d56aef04da3097007e2ab79614f2060607d4331162169733b1208d9377"
SCHEMA: Final = "task26-telegram-transport-readiness-v3"
MONITORED: Final = (
    "customers/registry.json",
    "data/onboarding/telegram-customer-bootstrap-v1/ledger.json",
    "data/customers/task26_live_2e_r2_20260815_8527916639/nutrition-onboarding/transient/workflow.json",
    "data/customers/task26_live_2e_r2_20260815_8527916639/nutrition-onboarding/session.json",
    "data/onboarding/telegram-publication-outbox-v1/ledger.json",
    "data/telegram-ingress-receipts-v1-d0aacf0f4bdbb7c0.json",
)


class ProbeFailure(RuntimeError):
    """Fail-closed readiness outcome."""


@dataclass(frozen=True)
class ProfileBinding:
    profile: Path
    config_sha256: str
    env_sha256: str
    username: str
    bot_id: int
    token: str = field(repr=False)


def digest(path: Path) -> str:
    return hashlib.sha256(path.read_bytes()).hexdigest()


def secure_file(path: Path) -> None:
    if path.is_symlink():
        raise ProbeFailure(f"symlink profile authority: {path.name}")
    value = path.stat()
    if not stat.S_ISREG(value.st_mode) or value.st_uid != os.getuid():
        raise ProbeFailure(f"unsafe profile authority: {path.name}")
    if stat.S_IMODE(value.st_mode) & 0o077:
        raise ProbeFailure(f"non-private profile authority: {path.name}")


def load_profile_binding(profile: Path) -> ProfileBinding:
    """Read exactly ``profile`` without changing cwd, globals, or environment."""
    root = profile.expanduser().resolve(strict=True)
    if not root.is_dir() or root.is_symlink():
        raise ProbeFailure("profile directory unavailable")
    config_path = root / "config.yaml"
    env_path = root / ".env"
    secure_file(config_path)
    secure_file(env_path)

    yaml = importlib.import_module("yaml")
    document = yaml.safe_load(config_path.read_text(encoding="utf-8")) or {}
    if not isinstance(document, dict):
        raise ProbeFailure("invalid profile config")
    platforms = document.get("platforms")
    telegram = platforms.get("telegram") if isinstance(platforms, dict) else None
    if not isinstance(telegram, dict) or telegram.get("enabled") is not True:
        raise ProbeFailure("enabled Telegram configuration unavailable")
    extra = telegram.get("extra")
    adaptive = extra.get("adaptive_nutrition") if isinstance(extra, dict) else None
    separate = adaptive.get("separate_bot") if isinstance(adaptive, dict) else None
    username = separate.get("bot_username") if isinstance(separate, dict) else None
    if not isinstance(username, str) or not username.removeprefix("@").strip():
        raise ProbeFailure("configured Telegram bot username unavailable")
    username = username.removeprefix("@").strip()

    dotenv = importlib.import_module("dotenv")
    values = dotenv.dotenv_values(env_path, interpolate=False)
    token = values.get("TELEGRAM_BOT_TOKEN")
    if not isinstance(token, str) or not token.strip():
        raise ProbeFailure("configured Telegram token unavailable")
    token = token.strip()
    prefix, separator, _secret = token.partition(":")
    if not separator or not prefix.isdecimal() or int(prefix) <= 0:
        raise ProbeFailure("configured Telegram token identity invalid")
    return ProfileBinding(
        profile=root,
        config_sha256=digest(config_path),
        env_sha256=digest(env_path),
        username=username,
        bot_id=int(prefix),
        token=token,
    )


async def getme_gate(
    binding: ProfileBinding,
    bot_factory: Callable[[str], Any],
    *,
    timeout: float = 10.0,
) -> dict[str, object]:
    """Call only getMe and match both configured username and token-bound ID."""
    bot = bot_factory(binding.token)
    try:
        async with bot:
            identity = await asyncio.wait_for(bot.get_me(), timeout=timeout)
    except TimeoutError as exc:
        raise ProbeFailure("telegram getMe timeout") from exc
    except Exception as exc:
        raise ProbeFailure("telegram getMe transport failure") from exc
    returned_username = getattr(identity, "username", None)
    returned_id = getattr(identity, "id", None)
    if returned_username != binding.username or returned_id != binding.bot_id:
        raise ProbeFailure("telegram getMe bot identity mismatch")
    return {
        "schema": SCHEMA,
        "status": "READY_TELEGRAM_TRANSPORT",
        "successor": SUCCESSOR,
        "profile": str(binding.profile),
        "config_sha256": binding.config_sha256,
        "configured_username": binding.username,
        "bot_id_match": True,
        "method": "Bot.get_me",
        "bounded_timeout_seconds": timeout,
        "consumes_updates": False,
        "mutates_cursor": False,
        "writes_state": False,
    }


def production_bot_factory(token: str) -> Any:
    telegram = importlib.import_module("telegram")
    telegram_request = importlib.import_module("telegram.request")
    request = telegram_request.HTTPXRequest(
        connect_timeout=5.0,
        read_timeout=5.0,
        write_timeout=5.0,
        pool_timeout=5.0,
    )
    return telegram.Bot(token=token, request=request)


def authority_hashes(profile: Path) -> dict[str, str]:
    return {relative: digest(profile / relative) for relative in MONITORED}


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("--profile", required=True, type=Path)
    parser.add_argument("--timeout", default=10.0, type=float)
    args = parser.parse_args()
    if not 0 < args.timeout <= 10:
        print("FAIL_TELEGRAM_TRANSPORT: invalid timeout", file=sys.stderr)
        return 2
    try:
        binding = load_profile_binding(args.profile)
        before = authority_hashes(binding.profile)
        result = asyncio.run(
            getme_gate(binding, production_bot_factory, timeout=args.timeout)
        )
        if authority_hashes(binding.profile) != before:
            raise ProbeFailure("profile authority changed during getMe gate")
    except (ProbeFailure, OSError, ValueError) as exc:
        print(f"FAIL_TELEGRAM_TRANSPORT: {exc}", file=sys.stderr)
        return 3
    print(json.dumps(result, sort_keys=True, separators=(",", ":")))
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
