"""Canonical JSON-schema emission checks."""

from __future__ import annotations

import json
from datetime import date
from pathlib import Path

import pytest

from checkin_cli.weekly_operations_domain_adherence import (
    CanonicalAdherenceSignal,
    derive_canonical_adherence_signal,
)
from checkin_cli.weekly_operations_domain_trainer import (
    TrainerIntensity,
    TrainerSessionPayload,
)
from checkin_cli.models import (
    ContractStatus,
    Event,
    EventType,
    Provenance,
    Safety,
    SafetyReason,
    build_satisfaction_event,
    validate_event,
    build_schedule_reference_event,
    build_schedule_confirmation_event,
)
from checkin_cli.store import EventStore
from tests._support import invoke


def test_weekly_domain_models_are_defined_by_focused_modules() -> None:
    """Given public model imports, when inspected, then focused modules own them."""
    assert TrainerIntensity.__module__.endswith("weekly_operations_domain_trainer")
    assert TrainerSessionPayload.__module__.endswith("weekly_operations_domain_trainer")
    assert CanonicalAdherenceSignal.__module__.endswith(
        "weekly_operations_domain_adherence"
    )
    assert derive_canonical_adherence_signal(None, None).status == "missing"


def _customer_pilot_event(safety: Safety, status: ContractStatus) -> Event:
    return build_satisfaction_event(
        "client_001",
        score=8,
        collected_on=date(2026, 8, 1),
        status=status,
        safety=safety,
    )


def _typed_reason(source_flow: str) -> SafetyReason:
    return SafetyReason(
        class_="pain",
        source_flow=source_flow,
        matched_field="pain_summary",
        excerpt="knee pain",
        rule_id="S2",
    )


def test_pilot_typed_safety_reasons_pass_for_customer() -> None:
    reason = _typed_reason("customer_checkin")
    event = _customer_pilot_event(
        Safety(
            level="monitor",
            signals=("pain",),
            coaching_held=True,
            reasons=(reason,),
        ),
        ContractStatus.UNSAFE,
    )

    assert event.safety is not None
    assert event.safety.reasons == (reason,)
    assert validate_event(event) == event


@pytest.mark.parametrize(
    ("builder", "status"),
    (
        pytest.param(_customer_pilot_event, ContractStatus.UNSAFE, id="customer-unsafe"),
        pytest.param(_customer_pilot_event, ContractStatus.ACCEPTED, id="customer-held"),
    ),
)
def test_pilot_signal_only_unsafe_or_held_events_are_rejected(
    builder,
    status: ContractStatus,
) -> None:
    with pytest.raises(ValueError, match="typed SafetyReason"):
        builder(
            Safety(level="monitor", signals=("pain",), coaching_held=True),
            status,
        )


def test_pre_pilot_signal_only_safety_remains_compatible() -> None:
    event = Event(
        event_id="legacy_safety_flag",
        event_type=EventType.SAFETY_FLAG,
        occurred_at_kst="2026-08-01T08:00:00+09:00",
        recorded_at_kst="2026-08-01T08:00:00+09:00",
        provenance=Provenance(
            source_type="manual",
            source_ref="legacy",
            content_sha256="0" * 64,
        ),
        status=ContractStatus.UNSAFE,
        dedupe_key="legacy-safety-flag-key",
        safety=Safety(level="stop_and_escalate", signals=("pain",), coaching_held=True),
    )

    assert event.safety is not None
    assert event.safety.reasons == ()


def test_every_emitted_event_conforms_to_the_canonical_contract(tmp_path: Path) -> None:
    # Given: valid, incomplete, unsafe, and historical inputs.
    home = tmp_path / "coach-data"
    history = tmp_path / "D1.md"
    history.write_text("## D+1 — 2026-01-01\nprivate source text\n")
    invoke(home, "import-history", "--source", str(history), "--range-label", "D1")
    invoke(home, "record", "--message-id", "contract-valid", "--received-at", "2026-07-17T08:11:00+09:00", "--text", "체중 70.2 kg\n칼로리 2400 kcal")
    invoke(home, "record", "--message-id", "contract-incomplete", "--received-at", "2026-07-17T08:12:00+09:00", "--text", "체중 7O kg\n칼로리 2400 kcal")
    invoke(home, "record", "--message-id", "contract-unsafe", "--received-at", "2026-07-17T08:13:00+09:00", "--text", "체중 70.2 kg\n칼로리 2400 kcal\n가슴 통증")

    # When/Then: every persisted JSON event passes the package-owned contract.
    events = [json.loads(line) for line in (home / "events.jsonl").read_text().splitlines()]
    assert all(validate_event(event) for event in events)


def test_package_valid_event_is_appended_without_external_schema(tmp_path: Path) -> None:
    store = EventStore.for_standalone(tmp_path / "coach-data")
    event = build_satisfaction_event(
        "client_001",
        score=8,
        collected_on=date(2026, 8, 1),
        provenance=Provenance(
            source_type="fixture",
            source_ref="fixture",
            content_sha256="0" * 64,
        ),
    )

    stored = store.append_wizard_event(event)

    assert stored.event_id == event.event_id
    assert (tmp_path / "coach-data" / "events.jsonl").exists()


def test_malformed_occurred_at_kst_is_rejected_by_canonical_schema() -> None:
    event = build_satisfaction_event(
        "client_001",
        score=8,
        collected_on=date(2026, 8, 1),
        provenance=Provenance(
            source_type="manual",
            source_ref="fixture",
            content_sha256="0" * 64,
        ),
    )
    payload = event.model_dump(mode="json")
    payload["occurred_at_kst"] = "not-a-date"

    with pytest.raises(ValueError, match="canonical schema"):
        validate_event(payload)
@pytest.mark.parametrize("field_name", ("occurred_at_kst", "recorded_at_kst"))
def test_non_kst_offsets_are_rejected_by_canonical_schema(field_name: str) -> None:
    event = build_satisfaction_event(
        "client_001",
        score=8,
        collected_on=date(2026, 8, 1),
        provenance=Provenance(
            source_type="manual",
            source_ref="fixture",
            content_sha256="0" * 64,
        ),
    )
    payload = event.model_dump(mode="json")
    payload[field_name] = "2026-08-01T08:00:00+00:00"

    with pytest.raises(ValueError, match="canonical schema"):
        validate_event(payload)


def test_pre_pilot_events_preserve_legacy_payload_sidecars() -> None:
    event = Event(
        event_id="legacy_event",
        event_type=EventType.CHECK_IN_RECEIVED,
        occurred_at_kst="2026-08-01T08:00:00+09:00",
        recorded_at_kst="2026-08-01T08:00:00+09:00",
        schema_version="2.0",
        provenance=Provenance(
            source_type="manual",
            source_ref="legacy",
            content_sha256="0" * 64,
        ),
        status=ContractStatus.ACCEPTED,
        dedupe_key="legacy-payload-key",
        payload_ref="legacy-sidecar.json",
    )

    assert event.payload_ref == "legacy-sidecar.json"
def test_safety_reason_normalizes_del_character() -> None:
    reason = SafetyReason(
        class_="pain",
        source_flow="customer_checkin",
        matched_field="free_text",
        excerpt="chest\x7fpain",
        rule_id="S2",
    )

    assert "\x7f" not in reason.excerpt

def test_schedule_reference_requires_dual_confirmation_and_exact_confirmation_pin():
    reference = build_schedule_reference_event(
        "client_001", date(2026, 8, 1), "09:30:00",
        customer_confirmed=True, owner_confirmed=True, last_change_note="첫 합의 일정",
    )
    assert reference.event_type is EventType.SCHEDULE_REFERENCE
    assert reference.schedule_reference is not None
    with pytest.raises(ValueError, match="both confirmations"):
        build_schedule_reference_event(
            "client_001", date(2026, 8, 1), "09:30:00",
            customer_confirmed=False, owner_confirmed=True, last_change_note="불완전",
        )
    confirmation = build_schedule_confirmation_event(
        "client_001", reference.event_id, "a" * 64, "operator_1", "b" * 64,
    )
    assert confirmation.schedule_confirmation.reference_event_id == reference.event_id
