from __future__ import annotations

from collections.abc import Mapping
from dataclasses import asdict, dataclass
from pathlib import Path
from typing import Final, Protocol

from checkin_cli.nutrition_onboarding_contract import (
    MessageEvidence,
    OnboardingAuthority,
    OnboardingStatus,
    canonical_digest,
)
from checkin_cli.nutrition_onboarding_fs import read_private_json
from checkin_cli.nutrition_onboarding_models import OnboardingState
from checkin_cli.nutrition_onboarding_reconciliation import (
    load_reconciliation,
)
from checkin_cli.nutrition_onboarding_session import (
    build_status,
    load_mutable_session,
    save_session,
)


DISCLAIMER_VERSION: Final = "owner-risk-acceptance-ko-v1"


@dataclass(frozen=True, slots=True)
class OwnerRiskAcceptanceBinding:
    candidate_epoch: str
    bootstrap_session_id: str
    customer_key: str
    baseline_digest: str
    reconciliation_digest: str


@dataclass(frozen=True, slots=True)
class OwnerRiskAcceptanceRequest:
    binding: OwnerRiskAcceptanceBinding
    acceptance_identity: str
    authority: OnboardingAuthority
    evidence: MessageEvidence


@dataclass(frozen=True, slots=True)
class OwnerRiskAcceptanceFinalizationContext:
    customer_key: str
    baseline_digest: str
    reconciliation_digest: str


class OwnerRiskAcceptanceService(Protocol):
    @property
    def profile_root(self) -> Path: ...

    @property
    def customer_key(self) -> str: ...

    @property
    def root(self) -> Path: ...

    @property
    def session_path(self) -> Path: ...


def owner_risk_acceptance_identity(
    binding: OwnerRiskAcceptanceBinding,
) -> str:
    return canonical_digest(asdict(binding))


def validate_owner_risk_acceptance_receipt(
    value: object,
    *,
    context: OwnerRiskAcceptanceFinalizationContext,
) -> dict[str, object]:
    receipt = _string_dict(value)
    expected_fields = {
        "schema_version",
        "decision",
        "clinical_approval",
        "disclaimer_version",
        "candidate_epoch",
        "bootstrap_session_id",
        "customer_key",
        "baseline_digest",
        "reconciliation_digest",
        "acceptance_identity",
        "authority_digest",
        "actor",
        "telegram_evidence",
        "digest",
    }
    candidate_epoch = receipt.get("candidate_epoch")
    bootstrap_session_id = receipt.get("bootstrap_session_id")
    acceptance_identity = receipt.get("acceptance_identity")
    actor = _string_dict(receipt.get("actor"))
    telegram_evidence = _string_dict(
        receipt.get("telegram_evidence")
    )
    if (
        set(receipt) != expected_fields
        or receipt.get("schema_version")
        != "owner-risk-acceptance-v1"
        or receipt.get("decision")
        != "accepted_for_nonmedical_coaching"
        or receipt.get("clinical_approval") is not False
        or receipt.get("disclaimer_version") != DISCLAIMER_VERSION
        or not isinstance(candidate_epoch, str)
        or len(candidate_epoch) != 64
        or set(candidate_epoch) - set("0123456789abcdef")
        or not isinstance(bootstrap_session_id, str)
        or not bootstrap_session_id
        or receipt.get("customer_key") != context.customer_key
        or receipt.get("baseline_digest") != context.baseline_digest
        or receipt.get("reconciliation_digest")
        != context.reconciliation_digest
        or not isinstance(acceptance_identity, str)
        or set(actor) != {"user_id", "chat_id", "topic_id"}
        or any(type(actor.get(key)) is not int for key in actor)
        or set(telegram_evidence) != {"message_id", "update_id"}
        or any(
            type(telegram_evidence.get(key)) is not int
            for key in telegram_evidence
        )
    ):
        raise ValueError("owner risk acceptance receipt is invalid")
    binding = OwnerRiskAcceptanceBinding(
        candidate_epoch=candidate_epoch,
        bootstrap_session_id=bootstrap_session_id,
        customer_key=context.customer_key,
        baseline_digest=context.baseline_digest,
        reconciliation_digest=context.reconciliation_digest,
    )
    unsigned = {
        key: item for key, item in receipt.items() if key != "digest"
    }
    if (
        acceptance_identity != owner_risk_acceptance_identity(binding)
        or receipt.get("digest") != canonical_digest(unsigned)
    ):
        raise ValueError("owner risk acceptance receipt is invalid")
    return receipt


def validate_owner_risk_acceptance_history(
    value: object,
    *,
    context: OwnerRiskAcceptanceFinalizationContext,
) -> list[dict[str, object]]:
    if not isinstance(value, list) or not value:
        raise ValueError("owner risk acceptance history is invalid")
    return [
        validate_owner_risk_acceptance_receipt(
            receipt,
            context=context,
        )
        for receipt in value
    ]


def _string_dict(value: object) -> dict[str, object]:
    if not isinstance(value, dict):
        raise ValueError("owner risk acceptance receipt is invalid")
    result: dict[str, object] = {}
    for key, item in value.items():
        if not isinstance(key, str):
            raise ValueError("owner risk acceptance receipt is invalid")
        result[key] = item
    return result


def record_owner_risk_acceptance(
    service: OwnerRiskAcceptanceService,
    *,
    request: OwnerRiskAcceptanceRequest,
) -> OnboardingStatus:
    document = load_mutable_session(
        session_path=service.session_path,
        customer_key=service.customer_key,
        authority=request.authority,
        evidence=request.evidence,
        role="owner",
    )
    if document["state"] != OnboardingState.SAFETY_HOLD.value:
        raise ValueError("owner risk acceptance is not available")
    if (
        len(request.binding.candidate_epoch) != 64
        or set(request.binding.candidate_epoch)
        - set("0123456789abcdef")
    ):
        raise ValueError("owner risk acceptance candidate is invalid")
    reconciliation = document.get("reconciliation")
    reconciliation_digest: object | None = None
    if isinstance(reconciliation, Mapping):
        for key, value in reconciliation.items():
            if key == "digest":
                reconciliation_digest = value
                break
    if (
        not isinstance(reconciliation_digest, str)
        or len(reconciliation_digest) != 64
        or set(reconciliation_digest) - set("0123456789abcdef")
    ):
        raise ValueError("owner risk acceptance reconciliation is invalid")
    baseline_candidate = read_private_json(
        service.root / "baseline-candidate.json",
    )
    baseline_payload = baseline_candidate.get("baseline")
    if (
        not isinstance(baseline_payload, dict)
        or baseline_candidate.get("digest")
        != canonical_digest(baseline_payload)
        or document.get("baseline_digest")
        != baseline_candidate.get("digest")
    ):
        raise ValueError("owner risk acceptance baseline is invalid")
    validated_reconciliation = load_reconciliation(reconciliation)
    if (
        validated_reconciliation["digest"] != reconciliation_digest
        or baseline_candidate.get("input_reconciliation_digest")
        != reconciliation_digest
    ):
        raise ValueError("owner risk acceptance reconciliation is invalid")
    current_binding = OwnerRiskAcceptanceBinding(
        candidate_epoch=request.binding.candidate_epoch,
        bootstrap_session_id=request.binding.bootstrap_session_id,
        customer_key=service.customer_key,
        baseline_digest=str(document["baseline_digest"]),
        reconciliation_digest=reconciliation_digest,
    )
    if (
        request.binding != current_binding
        or request.acceptance_identity
        != owner_risk_acceptance_identity(current_binding)
    ):
        raise ValueError("owner risk acceptance binding is stale")
    receipt = {
        "schema_version": "owner-risk-acceptance-v1",
        "decision": "accepted_for_nonmedical_coaching",
        "clinical_approval": False,
        "disclaimer_version": DISCLAIMER_VERSION,
        "candidate_epoch": current_binding.candidate_epoch,
        "bootstrap_session_id": current_binding.bootstrap_session_id,
        "customer_key": service.customer_key,
        "baseline_digest": current_binding.baseline_digest,
        "reconciliation_digest": current_binding.reconciliation_digest,
        "acceptance_identity": request.acceptance_identity,
        "authority_digest": document["authority_digest"],
        "actor": {
            "user_id": request.evidence.actor_user_id,
            "chat_id": request.evidence.chat_id,
            "topic_id": request.evidence.topic_id,
        },
        "telegram_evidence": {
            "message_id": request.evidence.message_id,
            "update_id": request.evidence.update_id,
        },
    }
    receipt["digest"] = canonical_digest(receipt)
    existing = document.get("owner_risk_acceptance_receipts", [])
    if not isinstance(existing, list):
        raise ValueError("owner risk acceptance history is invalid")
    document.update(
        state=OnboardingState.OWNER_REVIEW.value,
        owner_reviewed=False,
        owner_review_receipt=None,
        owner_risk_acceptance_receipts=[*existing, receipt],
    )
    save_session(
        session_path=service.session_path,
        document=document,
        evidence=request.evidence,
    )
    return build_status(
        customer_key=service.customer_key,
        document=document,
    )


__all__ = [
    "DISCLAIMER_VERSION",
    "OwnerRiskAcceptanceBinding",
    "OwnerRiskAcceptanceFinalizationContext",
    "OwnerRiskAcceptanceRequest",
    "OwnerRiskAcceptanceService",
    "owner_risk_acceptance_identity",
    "record_owner_risk_acceptance",
    "validate_owner_risk_acceptance_history",
    "validate_owner_risk_acceptance_receipt",
]
