from __future__ import annotations

from datetime import date
from pathlib import Path

import pytest

import checkin_cli.customer_admin as customer_admin_module
from checkin_cli.customer_admin import (
    CustomerAdminError,
    commit_multi_customer_admission_migration,
)
from checkin_cli.customer_coaching import RegistryDocument
from checkin_cli.multi_customer_admission_migration import (
    apply_admission_migration,
    propose_admission_migration,
)
from checkin_cli.multi_customer_admission_migration_models import (
    AdmissionMigrationError,
)
from tests.multi_customer_testkit import (
    TEST_CANDIDATE_DIGEST,
    admission_proposal,
)
from tests.test_customer_admin import _activation_fixture, activate_customer


def test_capacity_migration_rejects_wrong_approval_without_mutation(
    tmp_path: Path,
) -> None:
    profile_root, registry_path, _, _ = _activation_fixture(tmp_path)
    before = registry_path.read_bytes()
    proposal = admission_proposal(registry_path, 5)

    with pytest.raises(CustomerAdminError, match="rejected"):
        commit_multi_customer_admission_migration(
            profile_root,
            proposal,
            "wrong approval",
        )

    assert registry_path.read_bytes() == before


def test_capacity_proposal_rejects_invalid_candidate_binding(
    tmp_path: Path,
) -> None:
    _, registry_path, _, _ = _activation_fixture(tmp_path)

    with pytest.raises(AdmissionMigrationError, match="candidate"):
        propose_admission_migration(
            registry_path.read_bytes(),
            candidate_digest="not-a-candidate",
            max_enabled_customers=5,
        )


def test_capacity_apply_rejects_stale_registry_bytes(tmp_path: Path) -> None:
    _, registry_path, _, _ = _activation_fixture(tmp_path)
    before = registry_path.read_bytes()
    _, proposal = propose_admission_migration(
        before,
        candidate_digest=TEST_CANDIDATE_DIGEST,
        max_enabled_customers=5,
    )

    with pytest.raises(AdmissionMigrationError, match="before"):
        apply_admission_migration(
            before + b"\n",
            proposal,
            proposal.approval_phrase,
        )


def test_capacity_proposal_rebinds_existing_successor_candidate(
    tmp_path: Path,
) -> None:
    # Given
    _, registry_path, _, _ = _activation_fixture(tmp_path)
    first, _ = propose_admission_migration(
        registry_path.read_bytes(),
        candidate_digest="a" * 64,
        max_enabled_customers=5,
    )

    # When
    successor, proposal = propose_admission_migration(
        first,
        candidate_digest="b" * 64,
        max_enabled_customers=5,
    )

    # Then
    document = RegistryDocument.model_validate_json(successor)
    assert document.admission_policy is not None
    assert document.admission_policy.candidate_digest == "b" * 64
    assert document.admission_policy.max_enabled_customers == 5
    assert proposal.before_sha256 != proposal.after_sha256


def test_capacity_migration_write_failure_rolls_back_exactly(
    tmp_path: Path,
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    profile_root, registry_path, data_root, checklist_path = _activation_fixture(
        tmp_path,
    )
    _ = activate_customer(
        profile_root,
        data_root,
        "client_001",
        checklist_path,
        kst_date=date(2026, 8, 1),
    )
    registry_before = registry_path.read_bytes()
    journal_path = profile_root / "data/customer-activation-journal.json"
    journal_before = journal_path.read_bytes()
    proposal = admission_proposal(registry_path, 5)
    atomic_write = customer_admin_module._atomic_write_bytes
    failed = False

    def fail_registry_write(path: Path, content: bytes) -> None:
        nonlocal failed
        if path == registry_path and not failed:
            failed = True
            raise OSError("registry write crash")
        atomic_write(path, content)

    monkeypatch.setattr(
        customer_admin_module,
        "_atomic_write_bytes",
        fail_registry_write,
    )
    with pytest.raises(CustomerAdminError, match="migration failed"):
        commit_multi_customer_admission_migration(
            profile_root,
            proposal,
            proposal.approval_phrase,
        )

    assert registry_path.read_bytes() == registry_before
    assert journal_path.read_bytes() == journal_before
    assert not (
        profile_root / "data/customer-activation-receipts/client_001.json"
    ).exists()


def test_capacity_migration_rebinds_valid_activation_after_registry_metadata_drift(
    tmp_path: Path,
) -> None:
    profile_root, registry_path, data_root, checklist_path = _activation_fixture(
        tmp_path,
    )
    _ = activate_customer(
        profile_root,
        data_root,
        "client_001",
        checklist_path,
        kst_date=date(2026, 8, 1),
    )
    registry = RegistryDocument.model_validate_json(registry_path.read_bytes())
    customer = registry.customers[0].model_copy(
        update={"display_name": "Updated display name"}
    )
    drifted = registry.model_copy(update={"customers": (customer,)})
    _ = registry_path.write_text(
        drifted.model_dump_json(indent=2) + "\n",
        encoding="utf-8",
    )
    proposal = admission_proposal(registry_path, 5)

    result = commit_multi_customer_admission_migration(
        profile_root,
        proposal,
        proposal.approval_phrase,
    )

    assert result == proposal.after_sha256
    receipt = (
        profile_root / "data/customer-activation-receipts/client_001.json"
    )
    assert receipt.is_file()
