from __future__ import annotations

import json
from datetime import date
from pathlib import Path

import pytest
from pydantic import JsonValue

import checkin_cli
import checkin_cli.customer_admin as customer_admin_module
from checkin_cli.customer_admin import (
    CustomerAdminError,
    validate_committed_activation,
    withdraw_customer,
)
from checkin_cli.customer_coaching import CustomerSpec, RegistryDocument
from tests.multi_customer_testkit import (
    add_second_customer,
    configure_capacity,
)
from tests.test_customer_admin import _activation_fixture, activate_customer


def test_two_customers_activate_with_independent_receipts(
    tmp_path: Path,
) -> None:
    profile_root, registry_path, first_root, checklist_path = _activation_fixture(
        tmp_path,
    )
    second_root = add_second_customer(profile_root, registry_path)
    activate_customer(
        profile_root,
        first_root,
        "client_001",
        checklist_path,
        kst_date=date(2026, 8, 1),
    )
    configure_capacity(profile_root, registry_path, 2)
    activate_customer(
        profile_root,
        second_root,
        "client_002",
        checklist_path,
        kst_date=date(2026, 8, 1),
    )

    assert validate_committed_activation(profile_root, customer_id="client_001")
    assert validate_committed_activation(profile_root, customer_id="client_002")
    receipt_root = profile_root / "data/customer-activation-receipts"
    assert (receipt_root / "client_001.json").is_file()
    assert (receipt_root / "client_002.json").is_file()
    registry = checkin_cli.load_customer_registry(registry_path, profile_root)
    assert {
        runtime.spec.customer_key: runtime.data_root
        for runtime in registry.customers
        if runtime.spec.enabled
    } == {
        "client_001": first_root,
        "client_002": second_root,
    }


@pytest.mark.parametrize("journal_state", ["committed", "prepared"])
def test_recovery_materializes_missing_customer_receipt(
    tmp_path: Path,
    journal_state: str,
) -> None:
    profile_root, registry_path, data_root, checklist_path = _activation_fixture(
        tmp_path,
    )
    configure_capacity(profile_root, registry_path, 2)
    activate_customer(
        profile_root,
        data_root,
        "client_001",
        checklist_path,
        kst_date=date(2026, 8, 1),
    )
    receipt_path = profile_root / "data/customer-activation-receipts/client_001.json"
    receipt_path.unlink()
    if journal_state == "prepared":
        journal_path = profile_root / "data/customer-activation-journal.json"
        journal = json.loads(journal_path.read_text(encoding="utf-8"))
        journal["state"] = "prepared"
        journal.pop("committed_at")
        journal_path.write_text(json.dumps(journal), encoding="utf-8")

    assert validate_committed_activation(profile_root, customer_id="client_001")
    assert receipt_path.is_file()


def test_multi_customer_receipt_tamper_fails_closed(tmp_path: Path) -> None:
    profile_root, registry_path, data_root, checklist_path = _activation_fixture(
        tmp_path,
    )
    configure_capacity(profile_root, registry_path, 2)
    activate_customer(
        profile_root,
        data_root,
        "client_001",
        checklist_path,
        kst_date=date(2026, 8, 1),
    )
    receipt_path = profile_root / "data/customer-activation-receipts/client_001.json"
    receipt = json.loads(receipt_path.read_text(encoding="utf-8"))
    receipt["owner_sha256"] = "0" * 64
    receipt_path.write_text(json.dumps(receipt), encoding="utf-8")

    with pytest.raises(CustomerAdminError, match="missing or stale"):
        validate_committed_activation(profile_root, customer_id="client_001")


def test_receipt_write_failure_rolls_back_activation_exactly(
    tmp_path: Path,
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    profile_root, registry_path, data_root, checklist_path = _activation_fixture(
        tmp_path,
    )
    configure_capacity(profile_root, registry_path, 2)
    registry_before = registry_path.read_bytes()

    def fail_receipt(
        _profile_root: Path,
        _registry_path: Path,
        _document: RegistryDocument,
        _spec: CustomerSpec,
        _journal: dict[str, JsonValue],
    ) -> dict[str, JsonValue]:
        raise OSError("receipt write crash")

    monkeypatch.setattr(
        customer_admin_module,
        "_materialize_customer_activation_receipt",
        fail_receipt,
    )
    with pytest.raises(CustomerAdminError, match="could not be committed"):
        activate_customer(
            profile_root,
            data_root,
            "client_001",
            checklist_path,
            kst_date=date(2026, 8, 1),
        )

    assert registry_path.read_bytes() == registry_before
    assert not (
        profile_root / "data/customer-activation-receipts/client_001.json"
    ).exists()
    journal = json.loads(
        (profile_root / "data/customer-activation-journal.json").read_text(
            encoding="utf-8"
        )
    )
    assert journal["state"] == "abandoned"


def test_disabling_latest_customer_preserves_other_customer_receipt(
    tmp_path: Path,
) -> None:
    profile_root, registry_path, first_root, checklist_path = _activation_fixture(
        tmp_path,
    )
    second_root = add_second_customer(profile_root, registry_path)
    configure_capacity(profile_root, registry_path, 2)
    for customer_key, data_root in (
        ("client_001", first_root),
        ("client_002", second_root),
    ):
        activate_customer(
            profile_root,
            data_root,
            customer_key,
            checklist_path,
            kst_date=date(2026, 8, 1),
        )

    withdraw_customer(
        profile_root,
        "client_002",
        kst_date=date(2026, 8, 4),
    )

    assert validate_committed_activation(profile_root, customer_id="client_001")
    runtime = customer_admin_module.load_runtime_customer_registry(profile_root)
    assert [
        item.spec.customer_key for item in runtime.customers if item.spec.enabled
    ] == ["client_001"]
