"""Sealed reminder lifecycle tests through real schedule and canonical adapters."""

from __future__ import annotations

from dataclasses import replace
from datetime import datetime
from pathlib import Path
import subprocess
import sys

import anyio
import pytest

from checkin_cli.weekly_operations import CustomerKey, WeeklyOperationsConflict
from checkin_cli.weekly_operations_customer_authority_factory import (
    open_canonical_checkin_customer_authority,
)
from checkin_cli.weekly_operations_lifecycle import (
    ReminderDependencies,
    ReminderOutcome,
    ReminderResult,
    reminder_reservation_authority,
    run_due_reminder,
)
from checkin_cli.weekly_reminder_ledger_authority import (
    LedgerReservation,
    acquire_registered_weekly_reminder_ledger_authority,
)
from checkin_cli.weekly_reminder_authority import (
    WeeklyReminderRequest,
    bind_weekly_reminder_customer,
)
from tests._weekly_operations_correlation_support import (
    event,
    registered_source_at,
    store_at,
)
from tests._weekly_operations_lifecycle_support import (
    DAY,
    KST,
    FakeReminderProvider,
    ProviderMode,
    CUSTOMER,
    binding_input,
    fixture_at,
)


@pytest.mark.parametrize(
    ("instant", "expected_calls"),
    (
        (datetime(2026, 8, 17, 19, 59, 59, tzinfo=KST), 0),
        (datetime(2026, 8, 17, 20, 0, 0, tzinfo=KST), 1),
        (datetime(2026, 8, 17, 22, 59, 59, tzinfo=KST), 1),
        (datetime(2026, 8, 17, 23, 0, 0, tzinfo=KST), 0),
    ),
)
def test_exact_reminder_boundaries(tmp_path: Path, instant: datetime, expected_calls: int) -> None:
    # Given
    fixture = fixture_at(tmp_path, instant)
    provider = FakeReminderProvider()

    # When
    result = anyio.run(
        run_due_reminder,
        fixture.request,
        ReminderDependencies(provider, lambda: fixture.request.bound_customer),
    )

    # Then
    assert provider.calls == expected_calls
    assert result.outcome is (ReminderOutcome.SENT_AUDITED if expected_calls else ReminderOutcome.NOT_DUE)
    fixture.close()


def test_response_before_reservation_has_zero_schedule_and_provider_effects(tmp_path: Path) -> None:
    fixture = fixture_at(tmp_path, datetime(2026, 8, 17, 20, tzinfo=KST), event("response-0001"))
    provider = FakeReminderProvider()

    result = anyio.run(run_due_reminder, fixture.request, ReminderDependencies(provider, lambda: fixture.request.bound_customer))

    assert result.outcome is ReminderOutcome.ANSWERED
    assert provider.calls == 0
    assert fixture.request.bound_customer.ledger.receipts() == ()
    fixture.close()


@pytest.mark.parametrize(
    ("mode", "outcome"),
    (
        (ProviderMode.SENT, ReminderOutcome.SENT_AUDITED),
        (ProviderMode.REJECTED, ReminderOutcome.EXPLICIT_REJECTION),
        (ProviderMode.KNOWN_FAILURE, ReminderOutcome.KNOWN_FAILURE),
        (ProviderMode.UNKNOWN, ReminderOutcome.UNKNOWN),
    ),
)
def test_provider_terminal_outcomes_never_retry(tmp_path: Path, mode: ProviderMode, outcome: ReminderOutcome) -> None:
    fixture = fixture_at(tmp_path, datetime(2026, 8, 17, 20, tzinfo=KST))
    provider = FakeReminderProvider(mode)
    dependencies = ReminderDependencies(provider, lambda: fixture.request.bound_customer)

    first = anyio.run(run_due_reminder, fixture.request, dependencies)
    second = anyio.run(run_due_reminder, fixture.request, dependencies)

    assert first.outcome is outcome
    assert second.receipt is not None
    assert provider.calls == 1
    assert len({row.reservation_id for row in fixture.request.bound_customer.ledger.receipts()}) == 1
    fixture.close()


def test_cross_customer_source_and_store_cannot_bind(tmp_path: Path) -> None:
    fixture = fixture_at(tmp_path, datetime(2026, 8, 17, 20, tzinfo=KST))

    with pytest.raises(WeeklyOperationsConflict, match="authorities disagree"):
        _ = bind_weekly_reminder_customer(
            replace(
                binding_input(tmp_path, fixture.stores, fixture.canonical),
                authorization=replace(
                    binding_input(tmp_path, fixture.stores, fixture.canonical).authorization,
                    customer_key=CustomerKey("pilot_customer_002"),
                ),
            )
        )
    fixture.close()


def test_prepared_receipt_with_stale_final_pins_abandons_without_provider(tmp_path: Path) -> None:
    fixture = fixture_at(tmp_path, datetime(2026, 8, 17, 20, tzinfo=KST))
    bound = fixture.request.bound_customer
    _ = bound.ledger.reserve(
        LedgerReservation(
            bound.customer_identity_digest,
            DAY,
            {"chat_id": bound.route_chat_id, "topic_id": bound.route_topic_id},
            bound.runtime_registry_digest,
            bound.config_digest,
            bound.authority_digest,
            bound.canonical.sequence,
            bound.canonical.digest,
            reminder_reservation_authority(bound),
        )
    )
    stale = bind_weekly_reminder_customer(
        replace(
            binding_input(tmp_path, fixture.stores, fixture.canonical),
            authorization=replace(
                binding_input(tmp_path, fixture.stores, fixture.canonical).authorization,
                config_digest="f" * 64,
            ),
        )
    )
    provider = FakeReminderProvider()

    result = anyio.run(run_due_reminder, fixture.request, ReminderDependencies(provider, lambda: stale))

    assert result.outcome is ReminderOutcome.AUTHORITY_INCIDENT
    assert result.receipt is not None and result.receipt.state == "abandoned"
    assert provider.calls == 0
    fixture.close()


def test_response_race_changes_canonical_pin_before_provider(tmp_path: Path) -> None:
    fixture = fixture_at(tmp_path, datetime(2026, 8, 17, 20, tzinfo=KST))
    provider = FakeReminderProvider()

    def respond_and_resnapshot():
        _ = fixture.canonical.transaction.append(event("racing-response"))
        return bind_weekly_reminder_customer(
            binding_input(tmp_path, fixture.stores, fixture.canonical)
        )

    result = anyio.run(run_due_reminder, fixture.request, ReminderDependencies(provider, respond_and_resnapshot))

    assert result.outcome is ReminderOutcome.AUTHORITY_INCIDENT
    assert provider.calls == 0
    fixture.close()


def test_bound_authority_is_the_only_provider_route(tmp_path: Path) -> None:
    fixture = fixture_at(tmp_path, datetime(2026, 8, 17, 20, tzinfo=KST))
    provider = FakeReminderProvider()

    result = anyio.run(run_due_reminder, fixture.request, ReminderDependencies(provider, lambda: fixture.request.bound_customer))

    assert result.outcome is ReminderOutcome.SENT_AUDITED
    assert provider.bound_digests == [fixture.request.bound_customer.authority_digest]
    assert tuple(WeeklyReminderRequest.__dataclass_fields__) == ("bound_customer", "kst_day", "now")
    fixture.close()


def test_provider_call_holds_exact_schedule_flock_against_another_process(tmp_path: Path) -> None:
    fixture = fixture_at(tmp_path, datetime(2026, 8, 17, 20, tzinfo=KST))
    provider = FakeReminderProvider(ProviderMode.BLOCKED)
    outcomes: list[ReminderResult] = []

    async def exercise() -> None:
        async with anyio.create_task_group() as tasks:
            async def tick() -> None:
                outcomes.append(await run_due_reminder(
                    fixture.request,
                    ReminderDependencies(provider, lambda: fixture.request.bound_customer),
                ))

            tasks.start_soon(tick)
            await provider.entered.wait()
            lock = tmp_path / "customer" / "data" / ".scheduled-deliveries.lock"
            probe = subprocess.run(
                [sys.executable, "-c", "import fcntl,os,sys; f=os.open(sys.argv[1],os.O_RDWR);\ntry: fcntl.flock(f,fcntl.LOCK_EX|fcntl.LOCK_NB)\nexcept BlockingIOError: print('blocked')\nelse: print('acquired')", str(lock)],
                check=True, capture_output=True, text=True,
            )
            assert probe.stdout.strip() == "blocked"
            provider.release.set()

    anyio.run(exercise)
    assert provider.calls == 1
    assert outcomes[0].outcome is ReminderOutcome.SENT_AUDITED
    fixture.close()


def test_registered_source_rejects_different_sidecar_authority_twice_without_ledger(
    tmp_path: Path,
) -> None:
    registered_store = store_at(tmp_path / "registered-authority", CUSTOMER)
    canonical = registered_source_at(
        tmp_path / "customer",
        CUSTOMER,
        registry_authority=registered_store.authority,
    )

    source = canonical.source
    expected = source.registered_binding
    for attempt in ("first", "restart"):
        mismatched_store = store_at(tmp_path / f"mismatched-{attempt}", CUSTOMER)
        with pytest.raises(
            WeeklyOperationsConflict, match="registered sidecar authority disagrees"
        ):
            _ = acquire_registered_weekly_reminder_ledger_authority(
                source, mismatched_store.store
            )
        mismatched_store.close()
        assert not (
            tmp_path / "customer" / "data" / "scheduled-deliveries.jsonl"
        ).exists()
        if attempt == "first":
            source.close()
            source = open_canonical_checkin_customer_authority(
                canonical.runtime, expected, registered_store.authority
            )

    source.close()
    registered_store.close()
