# Copyright (c) 2026 Nous Research
"""Canonical registry migration for a private channel inbox route."""

from __future__ import annotations

from dataclasses import dataclass, replace
import json
from pathlib import Path
import sys

import pytest

profile_package = str(Path(__file__).parents[2] / "dualcoach" / "profile")
if profile_package not in sys.path:
    sys.path.insert(0, profile_package)

from checkin_cli.channel_inbox_migration import (
    apply_registry_migration,
    plan_registry_migration,
    rollback_registry_migration,
)
from checkin_cli.channel_inbox_migration_models import (
    ChannelInboxMigrationError,
    ChannelInboxMigrationRequest,
    TelegramRoute,
)
from gateway.platforms.telegram_channel_inbox_config import (
    ChannelInboxAuthority,
    channel_inbox_capability_digest,
)


_CANDIDATE = "b" * 64
_AUTHORITY = ChannelInboxAuthority(
    direct_messages_chat_id="-1009000000001",
    parent_channel_id="-1008000000001",
    bot_id="8599834213",
    candidate_digest=_CANDIDATE,
)
_CAPABILITY = channel_inbox_capability_digest(_AUTHORITY)


def _weeks() -> list[dict[str, int | str | list[str]]]:
    return [
        {
            "week": week,
            "calories_kcal": 2300,
            "protein_g": 150,
            "meal_structure": ["아침", "점심", "저녁"],
        }
        for week in range(1, 13)
    ]


def _registry_bytes() -> bytes:
    payload = {
        "version": 1,
        "owner": {
            "user_id": "1",
            "chat_id": "-1001",
            "topic_id": "59",
        },
        "customers": [
            {
                "customer_key": "customer-1",
                "display_name": "고객 1",
                "enabled": True,
                "telegram": {
                    "user_id": "42",
                    "chat_id": "42",
                    "topic_id": "0",
                },
                "schedule": {
                    "daily_time": "08:00",
                    "weekly_weekday": 0,
                    "monthly_day": 1,
                },
                "profile": {"primary_goal": "식사 습관 안정"},
                "ai_processing_consent": {
                    "granted": True,
                    "recorded_on": "2026-08-26",
                    "notice_version": "privacy-v1",
                },
                "plan": {
                    "starts_on": "2026-08-26",
                    "focus": "nutrition_90_training_10",
                    "weeks": _weeks(),
                },
            },
            {
                "customer_key": "disabled-history",
                "display_name": "이전 고객",
                "enabled": False,
                "telegram": {
                    "user_id": "77",
                    "chat_id": "77",
                    "topic_id": "0",
                },
                "schedule": {
                    "daily_time": "08:30",
                    "weekly_weekday": 1,
                    "monthly_day": 2,
                },
                "profile": {"primary_goal": "기록 보존"},
                "ai_processing_consent": {
                    "granted": False,
                    "notice_version": "privacy-v1",
                },
                "plan": {
                    "starts_on": "2026-08-26",
                    "focus": "nutrition_90_training_10",
                    "weeks": _weeks(),
                },
            },
        ],
    }
    return (json.dumps(payload, ensure_ascii=False, indent=2) + "\n").encode()


def _request() -> ChannelInboxMigrationRequest:
    return ChannelInboxMigrationRequest(
        customer_key="customer-1",
        telegram_user_id="42",
        direct_messages_topic_id="9007199254740",
        authority=_AUTHORITY,
        capability_digest=_CAPABILITY,
    )


@dataclass(frozen=True, slots=True)
class _UnsafePlanCase:
    customer_key: str
    user_id: str
    chat_id: str
    topic_id: str
    reason: str

    def request(self) -> ChannelInboxMigrationRequest:
        authority = replace(
            _AUTHORITY,
            direct_messages_chat_id=self.chat_id,
        )
        return ChannelInboxMigrationRequest(
            customer_key=self.customer_key,
            telegram_user_id=self.user_id,
            direct_messages_topic_id=self.topic_id,
            authority=authority,
            capability_digest=channel_inbox_capability_digest(authority),
        )


def test_registry_route_migration_round_trips_exact_bytes() -> None:
    before = _registry_bytes()
    proposal = plan_registry_migration(before, _request())

    migrated = apply_registry_migration(
        before,
        proposal,
        proposal.approval_phrase,
    )
    assert b'"chat_id": "-1009000000001"' in migrated
    assert b'"topic_id": "9007199254740"' in migrated
    assert rollback_registry_migration(migrated, proposal) == before


def test_registry_route_migration_rejects_wrong_approval() -> None:
    before = _registry_bytes()
    proposal = plan_registry_migration(before, _request())

    with pytest.raises(ChannelInboxMigrationError, match="approval"):
        apply_registry_migration(before, proposal, "ㅇㅇ 승인")


@pytest.mark.parametrize(
    "case",
    [
        _UnsafePlanCase("missing", "42", "-1009", "8", "customer"),
        _UnsafePlanCase("customer-1", "99", "-1009", "8", "identity"),
        _UnsafePlanCase("customer-1", "42", "-1001", "59", "collision"),
        _UnsafePlanCase("customer-1", "42", "-1009", "0", "topic"),
    ],
)
def test_registry_route_migration_rejects_unsafe_plan(
    case: _UnsafePlanCase,
) -> None:
    with pytest.raises(ChannelInboxMigrationError, match=case.reason):
        plan_registry_migration(
            _registry_bytes(),
            case.request(),
        )


def test_registry_route_migration_rejects_byte_drift() -> None:
    before = _registry_bytes()
    proposal = plan_registry_migration(before, _request())

    with pytest.raises(ChannelInboxMigrationError, match="before"):
        apply_registry_migration(
            before + b" ",
            proposal,
            proposal.approval_phrase,
        )


def test_registry_route_migration_rejects_forged_proposal() -> None:
    before = _registry_bytes()
    proposal = plan_registry_migration(before, _request())
    forged = replace(
        proposal,
        old_route=TelegramRoute("42", "43", "0"),
    )

    with pytest.raises(ChannelInboxMigrationError, match="proposal"):
        apply_registry_migration(
            before,
            forged,
            forged.approval_phrase,
        )


def test_registry_route_migration_rejects_noncanonical_registry() -> None:
    minimal = (
        json.dumps(
            {
                "version": 1,
                "owner": {"user_id": "1", "chat_id": "-1", "topic_id": "1"},
                "customers": [
                    {
                        "customer_key": "customer-1",
                        "enabled": True,
                        "telegram": {
                            "user_id": "42",
                            "chat_id": "42",
                            "topic_id": "0",
                        },
                        "ai_processing_consent": {"granted": True},
                    }
                ],
            },
            indent=2,
        )
        + "\n"
    ).encode()

    with pytest.raises(ChannelInboxMigrationError, match="registry"):
        _ = plan_registry_migration(minimal, _request())


def test_registry_route_migration_rejects_arbitrary_capability() -> None:
    forged = replace(_request(), capability_digest="0" * 64)

    with pytest.raises(ChannelInboxMigrationError, match="authority"):
        _ = plan_registry_migration(_registry_bytes(), forged)
