"""Pure bounded multi-customer admission migration core."""

from __future__ import annotations

import hashlib
import json
import re

from checkin_cli.customer_coaching import (
    RegistryAdmissionPolicy,
    RegistryDocument,
)
from checkin_cli.multi_customer_admission_migration_models import (
    AdmissionMigrationError,
    AdmissionMigrationProposal,
    AdmissionMigrationRejectReason,
)


def _sha256(payload: bytes) -> str:
    return hashlib.sha256(payload).hexdigest()


def _proposal_digest(
    before_sha256: str,
    after_sha256: str,
    candidate_digest: str,
    max_enabled_customers: int,
) -> str:
    payload = json.dumps(
        {
            "after_sha256": after_sha256,
            "before_sha256": before_sha256,
            "capability": "nutricoach_multi_customer_v1",
            "candidate_digest": candidate_digest,
            "max_enabled_customers": max_enabled_customers,
        },
        sort_keys=True,
        separators=(",", ":"),
    ).encode("utf-8")
    return _sha256(payload)


def _parse_registry(payload: bytes) -> RegistryDocument:
    try:
        return RegistryDocument.model_validate_json(payload)
    except (TypeError, ValueError) as exc:
        raise AdmissionMigrationError(AdmissionMigrationRejectReason.REGISTRY) from exc


def propose_admission_migration(
    before: bytes,
    *,
    candidate_digest: str,
    max_enabled_customers: int,
) -> tuple[bytes, AdmissionMigrationProposal]:
    """Build a deterministic, non-persisting capacity transition."""
    if re.fullmatch(r"[0-9a-f]{64}", candidate_digest) is None:
        raise AdmissionMigrationError(AdmissionMigrationRejectReason.CANDIDATE)
    document = _parse_registry(before)
    if document.registry_mode != "ordinary_v1":
        raise AdmissionMigrationError(AdmissionMigrationRejectReason.MODE)
    try:
        current = document.admission_policy
        if current is None:
            policy = RegistryAdmissionPolicy(
                schema="nutricoach-multi-customer-v1",
                candidate_digest=candidate_digest,
                max_enabled_customers=max_enabled_customers,
            )
        elif (
            current.schema_name == "nutricoach-multi-customer-v1"
            and current.max_enabled_customers == max_enabled_customers
        ):
            policy = current.model_copy(
                update={"candidate_digest": candidate_digest},
            )
        else:
            raise AdmissionMigrationError(AdmissionMigrationRejectReason.STATE)
        migrated = document.model_copy(update={"admission_policy": policy})
        migrated = RegistryDocument.model_validate(migrated.model_dump(mode="json"))
    except AdmissionMigrationError:
        raise
    except (TypeError, ValueError) as exc:
        raise AdmissionMigrationError(AdmissionMigrationRejectReason.CAPACITY) from exc
    after = (migrated.model_dump_json(indent=2) + "\n").encode("utf-8")
    before_sha256 = _sha256(before)
    after_sha256 = _sha256(after)
    proposal = AdmissionMigrationProposal(
        before_sha256=before_sha256,
        after_sha256=after_sha256,
        candidate_digest=candidate_digest,
        max_enabled_customers=max_enabled_customers,
        proposal_digest=_proposal_digest(
            before_sha256,
            after_sha256,
            candidate_digest,
            max_enabled_customers,
        ),
    )
    return after, proposal


def apply_admission_migration(
    before: bytes,
    proposal: AdmissionMigrationProposal,
    approval_phrase: str,
) -> bytes:
    """Return exact after-bytes only for the sealed proposal and approval."""
    if _sha256(before) != proposal.before_sha256:
        raise AdmissionMigrationError(AdmissionMigrationRejectReason.BEFORE)
    after, expected = propose_admission_migration(
        before,
        candidate_digest=proposal.candidate_digest,
        max_enabled_customers=proposal.max_enabled_customers,
    )
    if expected != proposal:
        raise AdmissionMigrationError(AdmissionMigrationRejectReason.PROPOSAL)
    if approval_phrase != proposal.approval_phrase:
        raise AdmissionMigrationError(AdmissionMigrationRejectReason.APPROVAL)
    if _sha256(after) != proposal.after_sha256:
        raise AdmissionMigrationError(AdmissionMigrationRejectReason.AFTER)
    return after
