from __future__ import annotations

import sys
from pathlib import Path

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

from checkin_cli.wizard import WizardService

from gateway.platforms.physique_checkin import (
    CallbackData,
    PhysiqueCheckinBridge,
    ReceiptVariant,
)
from gateway.platforms.physique_checkin_config import PhysiqueCheckinConfig
from gateway.platforms.physique_checkin_prompts import WizardPrompt

NOW = 4_000_000_000
UNKNOWN_STEPS = (
    "bodyweight",
    "calories",
    "macros",
    "meals",
    "water",
    "sleep_duration",
    "sleep_quality",
    "digestion",
    "condition",
    "appetite_stress",
    "training_summary",
)


def _config() -> PhysiqueCheckinConfig:
    config = PhysiqueCheckinConfig.from_extra({
        "physique_checkin": {
            "enabled": True,
            "owner_id": "customer-ux",
            "chat_id": "customer-dm",
            "topic_id": "0",
            "expires_seconds": 3600,
        }
    })
    assert config is not None
    return config


def _service(tmp_path: Path) -> WizardService:
    return WizardService.for_standalone(tmp_path / "wizard")


def _bridge(tmp_path: Path) -> tuple[PhysiqueCheckinBridge, WizardService]:
    service = _service(tmp_path)
    return (
        PhysiqueCheckinBridge(
            _config(),
            service=service,
            binding_path=tmp_path / "bindings.json",
        ),
        service,
    )


def _start_nutrition_daily(bridge: PhysiqueCheckinBridge) -> str:
    launcher = bridge.open_launcher("nutrition_daily", message_id="44", now_epoch=NOW)
    assert launcher.callback_data is not None
    callback = CallbackData.parse(launcher.callback_data)
    assert callback is not None
    started = bridge.handle_callback(
        launcher.callback_data,
        "customer-ux",
        "customer-dm",
        "0",
        "44",
        now_epoch=NOW,
    )
    assert started.accepted is True
    return callback.session_id


def _current_actions(prompt: WizardPrompt) -> tuple[str, ...]:
    return tuple(
        parsed.action
        for _, data in prompt.button_rows[-1]
        if (parsed := CallbackData.parse(data)) is not None
    )


def _click(bridge: PhysiqueCheckinBridge, action: str, *, message_id: str = "44"):
    prompt = bridge.active_prompt()
    assert prompt is not None
    callback_data = next(
        data
        for row in prompt.button_rows
        for _, data in row
        if (parsed := CallbackData.parse(data)) is not None and parsed.action == action
    )
    return bridge.handle_callback(
        callback_data,
        "customer-ux",
        "customer-dm",
        "0",
        message_id,
        now_epoch=NOW,
    )


def _summary_bridge(tmp_path: Path) -> tuple[PhysiqueCheckinBridge, str]:
    bridge, _ = _bridge(tmp_path)
    session_id = _start_nutrition_daily(bridge)
    for _ in UNKNOWN_STEPS:
        reply = _click(bridge, "u")
        assert reply.accepted is True
    reply = _click(bridge, "a0")
    assert reply.accepted is True
    prompt = bridge.active_prompt()
    assert prompt is not None
    assert prompt.text.startswith("확인")
    return bridge, session_id


def test_nutrition_daily_unknown_action_is_u_and_advances_once(tmp_path: Path) -> None:
    bridge, _ = _bridge(tmp_path)
    _ = _start_nutrition_daily(bridge)

    prompt = bridge.active_prompt()
    assert prompt is not None
    assert _current_actions(prompt) == ("u",)

    reply = _click(bridge, "u")

    assert reply.accepted is True
    assert reply.receipt_variant is ReceiptVariant.UNKNOWN
    assert reply.prompt is not None
    assert reply.prompt.text.startswith("✅ 잘 모르겠어요로 반영했습니다.\n\n")
    snapshot = bridge.active_checkin_snapshot()
    assert snapshot is not None
    assert snapshot["step"] == "calories"


def test_nutrition_daily_summary_counts_unknown_steps_once_with_optional_skip(
    tmp_path: Path,
) -> None:
    bridge, _ = _summary_bridge(tmp_path)

    prompt = bridge.active_prompt()
    assert prompt is not None
    assert prompt.text.startswith("확인 12/12")
    assert "70.2" not in prompt.text
    assert "2300" not in prompt.text
    assert "280 150 65" not in prompt.text


def test_nutrition_daily_current_l_callback_is_rejected_but_other_flows_keep_defer(
    tmp_path: Path,
) -> None:
    bridge, _ = _bridge(tmp_path)
    session_id = _start_nutrition_daily(bridge)
    prompt = bridge.active_prompt()
    assert prompt is not None
    forged = CallbackData(session_id, "bodyweight", 0, "l").encode()

    nutrition_reply = bridge.handle_callback(
        forged,
        "customer-ux",
        "customer-dm",
        "0",
        "44",
        now_epoch=NOW,
    )
    assert nutrition_reply.accepted is False
    assert nutrition_reply.receipt_variant is ReceiptVariant.PREVIOUS

    morning = PhysiqueCheckinBridge(
        _config(),
        service=_service(tmp_path / "morning"),
        binding_path=tmp_path / "morning-bindings.json",
    )
    morning_launch = morning.open_launcher("morning", message_id="44", now_epoch=NOW)
    assert morning_launch.callback_data is not None
    morning_session = CallbackData.parse(morning_launch.callback_data)
    assert morning_session is not None
    morning_reply = morning.handle_callback(
        morning_launch.callback_data,
        "customer-ux",
        "customer-dm",
        "0",
        "44",
        now_epoch=NOW,
    )
    assert morning_reply.accepted is True
    morning_forged = CallbackData(morning_session.session_id, "bodyweight", 0, "l").encode()
    morning_reply = morning.handle_callback(
        morning_forged,
        "customer-ux",
        "customer-dm",
        "0",
        "44",
        now_epoch=NOW,
    )
    assert morning_reply.accepted is True


def test_nutrition_daily_receipt_contract_has_ten_distinct_typed_variants(
    tmp_path: Path,
) -> None:
    bridge, _ = _bridge(tmp_path)
    launch = bridge.open_launcher("nutrition_daily", message_id="44", now_epoch=NOW)
    assert launch.receipt_variant is ReceiptVariant.LAUNCH
    assert launch.prompt is None

    assert launch.callback_data is not None
    resume = bridge.handle_callback(
        launch.callback_data,
        "customer-ux",
        "customer-dm",
        "0",
        "44",
        now_epoch=NOW,
    )
    assert resume.receipt_variant is ReceiptVariant.RESUME
    assert resume.prompt is not None

    known = bridge.handle_text(
        "70.2", "customer-ux", "customer-dm", "0", now_epoch=NOW
    )
    assert known is not None
    assert known.receipt_variant is ReceiptVariant.KNOWN

    previous = _click(bridge, "p")
    assert previous.receipt_variant is ReceiptVariant.PREVIOUS

    unknown = _click(bridge, "u")
    assert unknown.receipt_variant is ReceiptVariant.UNKNOWN

    invalid = bridge.handle_callback(
        "pc1:bad:bodyweight:0:start",
        "customer-ux",
        "customer-dm",
        "0",
        "44",
        now_epoch=NOW,
    )
    assert invalid.receipt_variant is ReceiptVariant.INVALID

    stale = bridge.handle_callback(
        launch.callback_data,
        "other",
        "customer-dm",
        "0",
        "44",
        now_epoch=NOW,
    )
    assert stale.receipt_variant is ReceiptVariant.STALE

    summary_bridge, session_id = _summary_bridge(tmp_path / "summary")
    summary = _click(summary_bridge, "a1")
    assert summary.receipt_variant is ReceiptVariant.SUMMARY
    edited = summary_bridge.handle_callback(
        CallbackData(session_id, "edit_menu", 12, "e0").encode(),
        "customer-ux",
        "customer-dm",
        "0",
        "44",
        now_epoch=NOW,
    )
    assert edited.receipt_variant is ReceiptVariant.EDITED

    terminal_bridge, terminal_session_id = _summary_bridge(tmp_path / "terminal")
    terminal = terminal_bridge.handle_callback(
        CallbackData(terminal_session_id, "summary", 12, "a0").encode(),
        "customer-ux",
        "customer-dm",
        "0",
        "44",
        now_epoch=NOW,
    )
    assert terminal.receipt_variant is ReceiptVariant.TERMINAL

    variants = {
        launch.receipt_variant,
        resume.receipt_variant,
        known.receipt_variant,
        unknown.receipt_variant,
        edited.receipt_variant,
        previous.receipt_variant,
        invalid.receipt_variant,
        stale.receipt_variant,
        summary.receipt_variant,
        terminal.receipt_variant,
    }
    assert variants == set(ReceiptVariant)


def test_nutrition_daily_transition_headers_are_visible_value_free_and_truthful(
    tmp_path: Path,
) -> None:
    bridge, _ = _bridge(tmp_path)
    _ = _start_nutrition_daily(bridge)

    known = bridge.handle_text(
        "71.9", "customer-ux", "customer-dm", "0", now_epoch=NOW
    )
    assert known is not None and known.prompt is not None
    assert known.prompt.text.startswith("✅ 답변을 반영했습니다.\n\n")
    assert "71.9" not in known.prompt.text

    previous = _click(bridge, "p")
    assert previous.prompt is not None
    assert previous.prompt.text.startswith("↩️ 이전 답변을 수정합니다.\n\n")
    assert "저장" not in previous.prompt.text.split("\n\n", 1)[0]
    assert "71.9" not in previous.prompt.text

    unknown = _click(bridge, "u")
    assert unknown.prompt is not None
    assert unknown.prompt.text.startswith("✅ 잘 모르겠어요로 반영했습니다.\n\n")
    assert "71.9" not in unknown.prompt.text

    edit_bridge, session_id = _summary_bridge(tmp_path / "edit")
    _ = _click(edit_bridge, "a1")
    edited = edit_bridge.handle_callback(
        CallbackData(session_id, "edit_menu", 12, "e0").encode(),
        "customer-ux",
        "customer-dm",
        "0",
        "44",
        now_epoch=NOW,
    )
    assert edited.prompt is not None
    assert edited.prompt.text.startswith("✏️ 수정할 답변을 선택했습니다.\n\n")
    assert "71.9" not in edited.prompt.text


def test_nutrition_daily_non_commit_variants_have_no_false_receipt_header(
    tmp_path: Path,
) -> None:
    bridge, _ = _bridge(tmp_path)
    launch = bridge.open_launcher("nutrition_daily", message_id="44", now_epoch=NOW)
    assert launch.callback_data is not None
    resume = bridge.handle_callback(
        launch.callback_data,
        "customer-ux",
        "customer-dm",
        "0",
        "44",
        now_epoch=NOW,
    )
    invalid = bridge.handle_callback(
        "invalid", "customer-ux", "customer-dm", "0", "44", now_epoch=NOW
    )
    stale = bridge.handle_callback(
        launch.callback_data,
        "other",
        "customer-dm",
        "0",
        "44",
        now_epoch=NOW,
    )

    assert launch.prompt is None
    assert resume.prompt is not None
    assert not resume.prompt.text.startswith(("✅", "✏️", "↩️"))
    assert invalid.prompt is None
    assert stale.prompt is None


def test_nutrition_daily_history_navigation_label_is_exact_previous_answer_edit(
    tmp_path: Path,
) -> None:
    bridge, _ = _bridge(tmp_path)
    _ = _start_nutrition_daily(bridge)
    _ = bridge.handle_text("70.2", "customer-ux", "customer-dm", "0", now_epoch=NOW)

    prompt = bridge.active_prompt()
    assert prompt is not None
    assert prompt.button_rows[-1][0][0] == "이전 답 수정"
    assert _current_actions(prompt) == ("p", "u")
