"""Live Telegram-like check-in behavior and correction tests."""

from __future__ import annotations

import json
from hashlib import sha256
from multiprocessing import get_context
from pathlib import Path

import pytest

from checkin_cli.store import EventStore, RecordRequest
from tests._support import invoke


def _record_duplicate_in_process(home: str) -> None:
    """Record one external message from an independent local process."""
    EventStore.for_standalone(Path(home)).record(RecordRequest("telegram-concurrent-duplicate", "2026-07-17T08:11:00+09:00", "체중 70.2 kg\n칼로리 2400 kcal", None))


def test_records_eligible_checkin_and_materializes_current_state(tmp_path: Path) -> None:
    # Given: a valid, new morning check-in.
    home = tmp_path / "coach-data"

    # When: the CLI records the check-in.
    response = invoke(home, "record", "--message-id", "telegram-101", "--received-at", "2026-07-17T08:11:00+09:00", "--text", "체중 70.2 kg\n수면 7.5시간\n칼로리 2400 kcal\n운동 Push")

    # Then: one eligible event and one current-state entry are observable.
    assert response["outcome"] == "recorded"
    current = json.loads((home / "views" / "current.json").read_text())
    assert current["eligible_checkins"][0]["weight_kg"] == 70.2
    assert current["eligible_checkins"][0]["calories_kcal"] == 2400


def test_marks_incomplete_values_for_clarification_without_current_state_mutation(tmp_path: Path) -> None:
    # Given: a malformed weight value.
    home = tmp_path / "coach-data"

    # When: the CLI records it.
    response = invoke(home, "record", "--message-id", "telegram-invalid", "--received-at", "2026-07-17T08:11:00+09:00", "--text", "체중 7O kg\n칼로리 2400 kcal")

    # Then: it asks for clarification and cannot enter the trend view.
    assert response["outcome"] == "needs_clarification"
    assert json.loads((home / "views" / "current.json").read_text())["eligible_checkins"] == []


def test_deduplicates_message_without_appending_a_second_event(tmp_path: Path) -> None:
    # Given: a recorded Telegram message.
    home = tmp_path / "coach-data"
    args = ("record", "--message-id", "telegram-duplicate", "--received-at", "2026-07-17T08:11:00+09:00", "--text", "체중 70.2 kg\n칼로리 2400 kcal")
    invoke(home, *args)

    # When: its identical message id arrives again.
    response = invoke(home, *args)

    # Then: it is rejected as a duplicate without an additional event.
    assert response["outcome"] == "duplicate"
    assert len((home / "events.jsonl").read_text().splitlines()) == 1


def test_records_one_event_when_32_processes_receive_the_same_message(tmp_path: Path) -> None:
    # Given: thirty-two independent local workers and no prior event.
    home = tmp_path / "coach-data"
    context = get_context("fork")
    workers = [context.Process(target=_record_duplicate_in_process, args=(str(home),)) for _ in range(32)]

    # When: all workers record the same external Telegram message concurrently.
    for worker in workers:
        worker.start()
    for worker in workers:
        worker.join()

    # Then: exactly one immutable event exists and every worker completed.
    assert all(worker.exitcode == 0 for worker in workers)
    assert len((home / "events.jsonl").read_text(encoding="utf-8").splitlines()) == 1


def test_correction_supersedes_previous_eligible_event(tmp_path: Path) -> None:
    # Given: an initially eligible check-in.
    home = tmp_path / "coach-data"
    first = invoke(home, "record", "--message-id", "telegram-original", "--received-at", "2026-07-17T08:11:00+09:00", "--text", "체중 70.2 kg\n칼로리 2400 kcal")

    # When: a correction references that event.
    response = invoke(home, "record", "--message-id", "telegram-correction", "--received-at", "2026-07-17T08:12:00+09:00", "--supersedes", first["event_id"], "--text", "체중 70.0 kg\n칼로리 2400 kcal")

    # Then: current state contains only the corrected value.
    assert response["outcome"] == "recorded"
    current = json.loads((home / "views" / "current.json").read_text())
    assert [item["weight_kg"] for item in current["eligible_checkins"]] == [70.0]


def test_records_chest_pain_safety_flag_without_trend_mutation(tmp_path: Path) -> None:
    # Given: a check-in containing an urgent symptom.
    home = tmp_path / "coach-data"

    # When: the CLI records it.
    response = invoke(home, "record", "--message-id", "telegram-urgent", "--received-at", "2026-07-17T08:11:00+09:00", "--text", "체중 70.2 kg\n칼로리 2400 kcal\n가슴 통증이 있어요")

    # Then: safety is flagged and no eligible trend is created.
    assert response["outcome"] == "urgent_safety"
    assert response["safety_flags"] == ["chest_pain"]
    assert json.loads((home / "views" / "current.json").read_text())["eligible_checkins"] == []


@pytest.mark.parametrize("text", ["체중 70.2 kg\n칼로리 -1 kcal", "체중 70.2 kg"])
def test_ineligible_input_never_changes_trend(tmp_path: Path, text: str) -> None:
    # Given: an invalid or incomplete check-in.
    home = tmp_path / "coach-data"

    # When: it is recorded.
    response = invoke(home, "record", "--message-id", f"telegram-{abs(hash(text))}", "--received-at", "2026-07-17T08:11:00+09:00", "--text", text)

    # Then: no eligible trend value appears.
    assert response["outcome"] == "needs_clarification"
    assert json.loads((home / "views" / "current.json").read_text())["eligible_checkins"] == []


def test_rejects_unknown_supersedes_reference(tmp_path: Path) -> None:
    # Given: a correction whose target does not exist.
    home = tmp_path / "coach-data"

    # When: the CLI receives the invalid correction reference.
    response = invoke(home, "record", "--message-id", "self-reference", "--received-at", "2026-07-17T08:11:00+09:00", "--supersedes", "unknown_event", "--text", "체중 70.2 kg\n칼로리 2400 kcal")

    # Then: no event is appended.
    assert response["outcome"] == "needs_clarification"
    assert not (home / "events.jsonl").exists()


def test_rejects_self_supersedes_reference(tmp_path: Path) -> None:
    # Given: a correction that names its deterministic event id as its target.
    home = tmp_path / "coach-data"
    message_id = "self-reference"
    received_at = "2026-07-17T08:11:00+09:00"
    own_event_id = f"checkin_{sha256((message_id + received_at).encode()).hexdigest()[:24]}"

    # When: the CLI receives that self-reference.
    response = invoke(home, "record", "--message-id", message_id, "--received-at", received_at, "--supersedes", own_event_id, "--text", "체중 70.2 kg\n칼로리 2400 kcal")

    # Then: no event is appended.
    assert response["outcome"] == "needs_clarification"
    assert not (home / "events.jsonl").exists()


def test_rejects_cyclic_supersedes_reference(tmp_path: Path) -> None:
    # Given: a pre-existing correction whose target points to this candidate id.
    home = tmp_path / "coach-data"
    candidate_id = "checkin_cycle_candidate"
    event = {"event_id": "checkin_existing", "event_type": "correction", "occurred_at_kst": "2026-07-17T08:10:00+09:00", "recorded_at_kst": "2026-07-17T08:10:00+09:00", "schema_version": "1.0", "status": "accepted", "supersedes": candidate_id, "provenance": {"source_type": "manual", "source_ref": "fixture", "content_sha256": "0" * 64}}
    home.mkdir()
    (home / "events.jsonl").write_text(json.dumps(event) + "\n")

    # When: a candidate is checked against that reference graph.
    rejected = EventStore.for_standalone(home).supersedes_is_valid(candidate_id, "checkin_existing")

    # Then: the graph cycle is rejected.
    assert rejected is False
