from __future__ import annotations

import os
from pathlib import Path

import pytest

from nutricoach_continuity.contract import (
    SOURCE_FILE_DIGEST,
    SOURCE_ROW_DIGEST,
    Phase,
    canonical_source_frame,
)
from nutricoach_continuity.faults import CrashAt, CrashFaultError
from nutricoach_continuity.journal import OneUseLedger, PhaseContext, PhaseJournal
from nutricoach_continuity.permission import load_permission
from nutricoach_continuity.recovery import TargetState, classify_target
from nutricoach_continuity.sealer import seal

from .real_support import build_permission

pytestmark = pytest.mark.skipif(
    "NUTRICOACH_TEST_CUSTOMER_KEY_FILE" not in os.environ,
    reason="private installed-store customer fixture is unavailable",
)

_OPERATIONS = (
    "before-open",
    "before-write",
    "after-write",
    "after-file-fsync",
    "after-rename",
    "after-dir-fsync",
)
_CHECKPOINTS = tuple(
    f"phase-{index:02d}-{phase.value}:{operation}"
    for index, phase in enumerate(tuple(Phase)[:9])
    for operation in _OPERATIONS
)


@pytest.mark.parametrize("checkpoint", _CHECKPOINTS)
def test_durable_checkpoint_crash_reloads_permission_journal_and_reservation(
    tmp_path: Path,
    checkpoint: str,
) -> None:
    package = build_permission(tmp_path / "fixture")
    permission, preseal = tmp_path / "permission", tmp_path / "preseal"
    _ = seal(package, permission, preseal)
    package.contract.authorization_root.mkdir(mode=0o700)
    package.contract.execution_root.mkdir(mode=0o700)
    ledger = OneUseLedger(package.contract.authorization_root, package.package_digest)
    ledger.reserve()
    context = PhaseContext(package.package_digest, SOURCE_ROW_DIGEST, SOURCE_FILE_DIGEST)
    journal = PhaseJournal(package.contract.execution_root, context, CrashAt(checkpoint))

    def advance_until_crash() -> None:
        for phase in tuple(Phase)[:9]:
            _ = journal.advance(phase)

    with pytest.raises(CrashFaultError, match="injected crash"):
        advance_until_crash()
    reloaded_package = load_permission(permission)
    reloaded = PhaseJournal(package.contract.execution_root, context)
    reloaded.reload()
    same_ledger = OneUseLedger(
        reloaded_package.contract.authorization_root,
        reloaded_package.package_digest,
    )
    same_ledger.require_pending()
    assert len(reloaded.records) <= 9


def test_second_launcher_and_second_recovery_are_refused(tmp_path: Path) -> None:
    package = build_permission(tmp_path)
    package.contract.authorization_root.mkdir(mode=0o700)
    ledger = OneUseLedger(package.contract.authorization_root, package.package_digest)
    ledger.reserve()
    with pytest.raises(RuntimeError, match="consumed"):
        ledger.reserve()
    ledger.consume("FAILED")
    with pytest.raises(RuntimeError, match="pending"):
        ledger.require_pending()
    with pytest.raises(RuntimeError, match="already consumed"):
        ledger.consume("FAILED")


@pytest.mark.parametrize("offset", range(1, 792))
def test_every_nonempty_strict_prefix_is_classified_separately(offset: int) -> None:
    frame = canonical_source_frame()
    decision = classify_target(frame[:offset], frame, intent_durable=True)
    assert decision.state is TargetState.STRICT_PREFIX
    assert frame[:offset] + decision.append_suffix == frame
