from __future__ import annotations

import asyncio
import json
import os
from datetime import datetime, timezone
from pathlib import Path
from types import SimpleNamespace
from unittest.mock import AsyncMock, Mock

import pytest

from gateway.config import PlatformConfig
from gateway.platforms.telegram import TelegramAdapter

from gateway.platforms.telegram_customer_bootstrap import (
    BootstrapError,
    BootstrapState,
    CustomerDraft,
    GenerationConflict,
    RecoverySlot,
    Role,
    RoomBootstrapStore,
    RoomBootstrapTransport,
    room_bootstrap_state_dir,
)
from gateway.platforms.telegram_customer_bootstrap_registration import (
    TelegramCustomerBootstrapRegistration,
)


PACKAGE_ROOT = Path(
    os.environ.get(
        "DUALCOACH_PROFILE_PACKAGE",
        "/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli",
    )
)


def draft() -> CustomerDraft:
    return CustomerDraft(
        customer_key="synthetic_001",
        display_name="Synthetic Customer",
        starts_on="2026-08-14",
        daily_time="08:00",
        weekly_weekday=0,
        monthly_day=1,
        calories_kcal=2200,
        protein_g=140,
        meals=("breakfast", "lunch", "dinner"),
    )


def test_customer_dm_invite_is_single_use_and_restart_safe(tmp_path: Path) -> None:
    state_dir = tmp_path / "bootstrap"
    store = RoomBootstrapStore(
        state_dir,
        now=lambda: datetime(2026, 8, 14, tzinfo=timezone.utc),
    )
    prepared = store.prepare_rehearsal_customer_invite(
        draft(),
        bot_username="dualcoachtestbot",
        owner_id="12",
    )
    token = prepared.customer_link.rsplit("=", 1)[1]

    claimed = store.claim_rehearsal_customer_invite(
        token,
        user_id="10",
        chat_id="10",
        message_id="100",
    )
    restarted = RoomBootstrapStore(
        state_dir,
        now=lambda: datetime(2026, 8, 14, tzinfo=timezone.utc),
    )

    assert claimed.state is BootstrapState.REGISTERING
    assert restarted.get(claimed.session_id) == claimed
    assert claimed.role_claim(Role.CUSTOMER).topic_id == "0"
    with pytest.raises(BootstrapError, match="unavailable"):
        restarted.claim_rehearsal_customer_invite(
            token,
            user_id="10",
            chat_id="10",
            message_id="101",
        )
    assert state_dir.stat().st_mode & 0o777 == 0o700
    assert (state_dir / "ledger.json").stat().st_mode & 0o777 == 0o600


def test_customer_activation_commits_only_the_exact_bound_session(
    tmp_path: Path,
) -> None:
    store = RoomBootstrapStore(tmp_path / "bootstrap")
    prepared = store.prepare_rehearsal_customer_invite(
        draft(),
        bot_username="dualcoachtestbot",
        owner_id="12",
    )
    claimed = store.claim_rehearsal_customer_invite(
        prepared.customer_link.rsplit("=", 1)[1],
        user_id="10",
        chat_id="10",
        message_id="100",
    )
    awaiting_consent = store.transition(
        claimed.session_id,
        expected_generation=claimed.generation,
        target=BootstrapState.AWAITING_CONSENT,
    )
    awaiting_activation = store.transition(
        awaiting_consent.session_id,
        expected_generation=awaiting_consent.generation,
        target=BootstrapState.AWAITING_ACTIVATION,
    )

    with pytest.raises(BootstrapError, match="identity"):
        store.activate_bound_customer(
            awaiting_activation.session_id,
            expected_generation=awaiting_activation.generation,
            customer_key="synthetic_001",
            customer_user_id="11",
            owner_id="12",
        )
    assert store.get(awaiting_activation.session_id) == awaiting_activation

    committed = store.activate_bound_customer(
        awaiting_activation.session_id,
        expected_generation=awaiting_activation.generation,
        customer_key="synthetic_001",
        customer_user_id="10",
        owner_id="12",
    )
    retried = store.activate_bound_customer(
        awaiting_activation.session_id,
        expected_generation=awaiting_activation.generation,
        customer_key="synthetic_001",
        customer_user_id="10",
        owner_id="12",
    )

    assert committed.state is BootstrapState.ACTIVE
    assert retried == committed
    assert committed.customer_key == "synthetic_001"
    assert committed.customer_draft.customer_user_id == "10"
    assert store.get(awaiting_activation.session_id) == committed


def test_consent_receipt_requires_exact_reserved_attempt(tmp_path: Path) -> None:
    store = RoomBootstrapStore(tmp_path / "bootstrap")
    prepared = store.prepare_rehearsal_customer_invite(
        draft(),
        bot_username="dualcoachtestbot",
        owner_id="12",
    )
    token = prepared.customer_link.rsplit("=", 1)[1]
    claimed = store.claim_rehearsal_customer_invite(
        token,
        user_id="10",
        chat_id="10",
        message_id="100",
    )
    awaiting = store.transition(
        claimed.session_id,
        expected_generation=claimed.generation,
        target=BootstrapState.AWAITING_CONSENT,
    )
    publication = store.reserve_consent_publication(
        awaiting.session_id,
        expected_generation=awaiting.generation,
    )
    attempt = store.reserve_recovery_attempt(
        publication.session_id,
        slot=RecoverySlot.CONSENT_CARD,
        chat_id="10",
        expected_generation=publication.generation,
    )

    with pytest.raises(GenerationConflict):
        store.bind_recovery_receipt(
            publication.session_id,
            slot=RecoverySlot.CONSENT_CARD,
            chat_id="10",
            attempt_generation=attempt.generation + 1,
            receipt_id="500",
        )
    receipted = store.bind_recovery_receipt(
        publication.session_id,
        slot=RecoverySlot.CONSENT_CARD,
        chat_id="10",
        attempt_generation=attempt.generation,
        receipt_id="500",
    )
    committed = store.reconcile_committed_consent(
        receipted.session_id,
        expected_generation=receipted.generation,
        publication_attempt=receipted.consent_publication_attempt,
        consent_card_message_id="500",
    )

    assert committed.state is BootstrapState.AWAITING_ACTIVATION
    assert committed.recovery_attempts == ()
    assert store.unresolved_attempts() == ()


def test_claimed_customer_registers_disabled_without_other_role(
    tmp_path: Path,
) -> None:
    profile_root = tmp_path / "profile"
    registry_path = profile_root / "customers" / "registry.json"
    registry_path.parent.mkdir(parents=True)
    registry_path.write_text(
        json.dumps(
            {
                "version": 1,
                "owner": {
                    "user_id": "12",
                    "chat_id": "-100",
                    "topic_id": "22",
                },
                "customers": [],
            }
        ),
        encoding="utf-8",
    )
    store = RoomBootstrapStore(profile_root / "bootstrap")
    prepared = store.prepare_rehearsal_customer_invite(
        draft(),
        bot_username="dualcoachtestbot",
        owner_id="12",
    )
    claimed = store.claim_rehearsal_customer_invite(
        prepared.customer_link.rsplit("=", 1)[1],
        user_id="10",
        chat_id="10",
        message_id="100",
    )

    result = TelegramCustomerBootstrapRegistration(
        profile_root,
        store,
        package_root=PACKAGE_ROOT,
    ).handoff_rehearsal_customer(claimed)
    document = json.loads(registry_path.read_text(encoding="utf-8"))

    assert result.session.state is BootstrapState.AWAITING_CONSENT
    assert result.consent_route.key == ("10", "10", "0")
    assert document["customers"][0]["enabled"] is False
    assert set(document["customers"][0]) == {
        "customer_key",
        "display_name",
        "enabled",
        "telegram",
        "ai_processing_consent",
        "schedule",
        "profile",
        "plan",
    }


def test_enabled_adapter_builds_the_customer_bootstrap_transport(
    tmp_path: Path,
) -> None:
    adapter = object.__new__(TelegramAdapter)
    adapter.config = PlatformConfig(
        enabled=True,
        token="test",
        extra={"room_bootstrap": {"enabled": True}},
    )
    adapter._room_bootstrap_transport = None
    adapter._get_nutrition_coaching = lambda: SimpleNamespace(
        profile_root=tmp_path,
        owner=SimpleNamespace(user_id="12"),
    )

    transport = adapter._get_room_bootstrap_transport()

    assert isinstance(transport, RoomBootstrapTransport)
    assert transport.owner_id == "12"
    assert transport.store.state_dir == room_bootstrap_state_dir(tmp_path)
    assert adapter._get_room_bootstrap_transport() is transport


def test_restart_recovery_registers_and_publishes_once(
    tmp_path: Path,
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    profile_root = tmp_path / "profile"
    profile_root.mkdir(mode=0o700)
    registry_path = profile_root / "customers" / "registry.json"
    registry_path.parent.mkdir(mode=0o700)
    registry_path.write_text(
        json.dumps(
            {
                "version": 1,
                "owner": {
                    "user_id": "12",
                    "chat_id": "-100",
                    "topic_id": "22",
                },
                "customers": [],
            }
        ),
        encoding="utf-8",
    )
    registry_path.chmod(0o600)
    monkeypatch.setenv("DUALCOACH_PROFILE_PACKAGE", str(PACKAGE_ROOT))
    store = RoomBootstrapStore(
        room_bootstrap_state_dir(profile_root),
        now=lambda: datetime(2026, 8, 14, tzinfo=timezone.utc),
    )
    prepared = store.prepare_rehearsal_customer_invite(
        draft(),
        bot_username="dualcoachtestbot",
        owner_id="12",
    )
    claimed = store.claim_rehearsal_customer_invite(
        prepared.customer_link.rsplit("=", 1)[1],
        user_id="10",
        chat_id="10",
        message_id="100",
    )
    transport = RoomBootstrapTransport(store, owner_id="12")
    nutrition = SimpleNamespace(
        profile_root=profile_root,
        owner=SimpleNamespace(user_id="12"),
        refresh_live_registry=Mock(return_value=True),
        resolve=Mock(return_value=None),
        open_customer_onboarding=Mock(
            return_value=SimpleNamespace(text="consent", buttons=())
        ),
    )
    adapter = object.__new__(TelegramAdapter)
    adapter._room_bootstrap_transport = transport
    adapter._get_nutrition_coaching = lambda: nutrition
    adapter._get_nutrition_onboarding_runtime = lambda: None
    adapter._nutrition_onboarding_markup = lambda _card: None
    adapter._send_nutrition_topic = AsyncMock(
        return_value=SimpleNamespace(message_id=500)
    )

    asyncio.run(adapter._recover_room_bootstrap_waiting_states())
    asyncio.run(adapter._recover_room_bootstrap_waiting_states())

    recovered = store.get(claimed.session_id)
    document = json.loads(registry_path.read_text(encoding="utf-8"))
    assert recovered.state is BootstrapState.AWAITING_CONSENT
    assert recovered.consent_card_message_id == "500"
    assert document["customers"][0]["enabled"] is False
    adapter._send_nutrition_topic.assert_awaited_once()
