"""Artifact-specific fail-closed readiness validators."""

from __future__ import annotations

from collections.abc import Mapping
from dataclasses import dataclass
from datetime import datetime
from typing import cast

from checkin_cli.nutrition_onboarding_owner_risk import (
    OwnerRiskAcceptanceFinalizationContext,
    validate_owner_risk_acceptance_receipt,
)
from checkin_cli.nutrition_readiness_contract import (
    BASELINE_FIELDS,
    CALCULATION_FIELDS,
    DIGEST_PATTERN,
    KB_FIELDS,
    KST,
    POLICY_FIELDS,
    RECONCILIATION_FIELDS,
)
from checkin_cli.nutrition_readiness_io import (
    number_in_range,
    positive_number,
    required,
    valid_digest,
    valid_rule_list,
    valid_week_targets,
)


@dataclass(frozen=True, slots=True)
class Validation:
    reasons: tuple[str, ...]
    digest: str | None


def _result(reasons: set[str], document: Mapping[str, object] | None) -> Validation:
    digest = None if reasons or document is None else cast(str, document["digest"])
    return Validation(tuple(sorted(reasons)), digest)


def validate_kb(document: Mapping[str, object] | None) -> Validation:
    reasons: set[str] = set()
    if document is None:
        reasons.add("restriction_kb_missing")
    elif not required(document, KB_FIELDS) or not valid_digest(document):
        reasons.add("restriction_kb_stale")
    elif document.get("approved") is not True:
        reasons.add("restriction_kb_unapproved")
    elif (
        not isinstance(document.get("sources"), list)
        or not document["sources"]
        or any(
            not valid_rule_list(document.get(field))
            for field in (
                "allergens",
                "intolerances",
                "religious_ethical_exclusions",
                "medication_condition_rules",
                "hard_contraindications",
                "cross_contact_rules",
                "substitution_rules",
            )
        )
    ):
        reasons.add("restriction_sources_incomplete")
    return _result(reasons, document)


def validate_clinical_clearance(
    document: Mapping[str, object] | None,
    baseline: Mapping[str, object] | None,
) -> Validation:
    reasons: set[str] = set()
    expected_digest = (
        baseline.get("clinical_clearance_receipt")
        if baseline is not None
        else None
    )
    if (
        document is None
        or not isinstance(expected_digest, str)
        or set(document)
        != {
            "schema_version",
            "cleared_for_nonmedical_coaching",
            "external_reference",
            "baseline_digest",
            "digest",
        }
        or document.get("schema_version") != "1.0"
        or document.get("cleared_for_nonmedical_coaching") is not True
        or document.get("baseline_digest")
        != (baseline.get("source_baseline_digest") if baseline else None)
        or document.get("digest") != expected_digest
        or not valid_digest(document)
    ):
        reasons.add("baseline_invalid")
    return _result(reasons, document)


def validate_baseline(
    document: Mapping[str, object] | None,
    customer_key: str,
) -> Validation:
    reasons: set[str] = set()
    if document is None:
        reasons.add("baseline_missing")
    elif (
        document.get("schema_version") != "2.0"
        or not required(document, BASELINE_FIELDS)
        or not valid_digest(document)
    ):
        reasons.add("baseline_schema_invalid")
    else:
        if document.get("customer_key") != customer_key:
            reasons.add("baseline_schema_invalid")
        if not number_in_range(
            document.get("height_cm"), 100.0, 250.0
        ) or not number_in_range(document.get("weight_kg"), 30.0, 350.0):
            reasons.add("measurement_implausible")
        adult_age = document.get("adult_age")
        if (
            not isinstance(adult_age, int)
            or isinstance(adult_age, bool)
            or not 18 <= adult_age <= 120
        ):
            reasons.add("customer_not_adult")
        if not document.get("customer_attested_at_kst"):
            reasons.add("baseline_not_customer_attested")
        if not document.get("owner_review_receipt"):
            reasons.add("baseline_owner_review_missing")
        input_reconciliation_digest = document.get(
            "input_reconciliation_digest"
        )
        if (
            not isinstance(input_reconciliation_digest, str)
            or DIGEST_PATTERN.fullmatch(input_reconciliation_digest) is None
        ):
            reasons.add("baseline_schema_invalid")
        owner_risk_receipt = document.get(
            "owner_risk_acceptance_receipt"
        )
        if owner_risk_receipt is not None:
            try:
                validate_owner_risk_acceptance_receipt(
                    owner_risk_receipt,
                    context=OwnerRiskAcceptanceFinalizationContext(
                        customer_key=customer_key,
                        baseline_digest=str(
                            document.get("source_baseline_digest")
                        ),
                        reconciliation_digest=str(
                            input_reconciliation_digest
                        ),
                    ),
                )
            except ValueError:
                reasons.add("baseline_invalid")
        if (
            document.get("pregnancy_breastfeeding")
            not in {False, "not_applicable"}
            and not (
                document.get("clinical_clearance_receipt")
                or owner_risk_receipt
            )
        ):
            reasons.add(
                "pregnancy_breastfeeding_requires_clinical_review"
            )
        if (
            document.get("conditions")
            or document.get("medications")
        ) and not (
            document.get("clinical_clearance_receipt")
            or owner_risk_receipt
        ):
            reasons.add("medical_clearance_required")
    return _result(reasons, document)


def validate_reconciliation(
    document: Mapping[str, object] | None,
    baseline: Mapping[str, object] | None,
    kb: Mapping[str, object] | None,
) -> Validation:
    reasons: set[str] = set()
    if (
        document is None
        or not required(document, RECONCILIATION_FIELDS)
        or not valid_digest(document)
        or baseline is None
        or kb is None
        or document.get("baseline_digest") != baseline.get("digest")
        or document.get("restriction_kb_digest") != kb.get("digest")
        or document.get("resolution_status") != "resolved"
    ):
        reasons.add("customer_restrictions_unresolved")
    elif document.get("unresolved_hard_contraindications") != []:
        reasons.add("hard_contraindication_unresolved")
    return _result(reasons, document)


def validate_calculation(
    document: Mapping[str, object] | None,
    baseline: Mapping[str, object] | None,
    kb: Mapping[str, object] | None,
) -> Validation:
    reasons: set[str] = set()
    if document is None:
        reasons.add("calculation_missing")
    elif not required(document, CALCULATION_FIELDS) or not valid_digest(document):
        reasons.add("calculation_inputs_stale")
    else:
        if document.get("method_id") != "mifflin_st_jeor_1990":
            reasons.add("calculation_method_unapproved")
        if document.get("goal_trajectory_safe") is not True:
            reasons.add("goal_trajectory_unsafe")
        requested_safe = document.get(
            "requested_trajectory_within_guardrail"
        )
        recommended_date = document.get(
            "recommended_target_date"
        )
        if (
            not isinstance(requested_safe, bool)
            or (requested_safe and recommended_date is not None)
            or (
                not requested_safe
                and not isinstance(recommended_date, str)
            )
        ):
            reasons.add("goal_trajectory_unsafe")
        if not document.get("reviewed_by") or document.get("review_decision") != "approved":
            reasons.add("calculation_review_missing")
        if not valid_week_targets(
            document.get("weeks"),
            document.get("minimum_calories_kcal"),
            document.get("maximum_calories_kcal"),
        ):
            reasons.add("weekly_targets_incomplete")
        if baseline is not None and document.get("baseline_digest") != baseline.get("digest"):
            reasons.add("calculation_inputs_stale")
        if kb is not None and document.get("restriction_kb_digest") != kb.get("digest"):
            reasons.add("calculation_inputs_stale")
    return _result(reasons, document)


def validate_policy(document: Mapping[str, object] | None) -> Validation:
    reasons: set[str] = set()
    if document is None:
        reasons.add("adjustment_policy_missing")
    elif (
        not required(document, POLICY_FIELDS)
        or not valid_digest(document)
        or document.get("approved") is not True
        or not isinstance(document.get("minimum_observation_days"), int)
        or isinstance(document.get("minimum_observation_days"), bool)
        or cast(int, document["minimum_observation_days"]) < 7
        or not positive_number(document.get("maximum_step_kcal"))
        or document.get("manual_override_receipt_required") is not True
        or not document.get("reviewed_by")
        or any(
            not document.get(field)
            for field in (
                "adherence_inputs",
                "actual_intake_inputs",
                "activity_change_inputs",
                "adjustment_thresholds",
                "escalation_rules",
                "safety_stop_rules",
            )
        )
    ):
        reasons.add("adjustment_policy_unapproved")
    return _result(reasons, document)


def validate_receipt(
    document: Mapping[str, object] | None,
    expected: Mapping[str, object],
    now: datetime,
) -> Validation:
    reasons: set[str] = set()
    if document is None:
        reasons.add("readiness_receipt_missing")
    elif not valid_digest(document):
        reasons.add("readiness_receipt_invalid")
    else:
        if any(document.get(key) != value for key, value in expected.items()):
            reasons.add("authority_receipt_stale")
        if (
            document.get("privacy_consent_version") != "privacy-v1"
            or not document.get("privacy_consent_digest")
        ):
            reasons.add("privacy_consent_missing")
        if not document.get("owner_review_receipt"):
            reasons.add("authority_receipt_stale")
        if document.get("delivery_enabled") is not False:
            reasons.add("delivery_enabled")
        if document.get("activation_enabled") is not False:
            reasons.add("feature_flags_enabled")
        try:
            expires_at = datetime.fromisoformat(str(document.get("expires_at_kst")))
        except ValueError:
            reasons.add("authority_receipt_stale")
        else:
            if expires_at.tzinfo is None or expires_at.astimezone(KST) <= now:
                reasons.add("authority_receipt_stale")
    return _result(reasons, document)
