"""Canonical event projection for weekly wizard correlation."""

from __future__ import annotations

from collections.abc import Mapping, Sequence
from datetime import date, timedelta
from typing import Final, TypeAlias, TypeGuard

from pydantic import JsonValue, TypeAdapter

from .models import ContractCheckin, Event, EventType, TrainerSessionPayload

WizardScalar: TypeAlias = str | int | float | bool | None
WizardValue: TypeAlias = (
    WizardScalar
    | Event
    | ContractCheckin
    | TrainerSessionPayload
    | Mapping[str, "WizardValue"]
    | Sequence["WizardValue"]
)
EventRecord: TypeAlias = Event | Mapping[str, WizardValue]
ProjectionSource: TypeAlias = Mapping[str, WizardValue] | Sequence[EventRecord]

WIZARD_MAPPING_ADAPTER: Final[TypeAdapter[dict[str, JsonValue]]] = TypeAdapter(
    dict[str, JsonValue]
)


def string_mapping(value: WizardValue) -> TypeGuard[Mapping[str, WizardValue]]:
    """Narrow the string-keyed mapping contract used by canonical models."""
    return isinstance(value, Mapping)


def numeric_float(value: WizardValue) -> float | None:
    """Parse only the scalar numeric forms accepted by wizard answers."""
    if not isinstance(value, (bool, int, float, str)):
        return None
    try:
        return float(value)
    except ValueError:
        return None


def _mapping_value(source: Mapping[str, WizardValue], names: tuple[str, ...]) -> WizardValue:
    for name in names:
        value = source.get(name)
        if value is not None:
            return value
    return None


def event_value(
    source: EventRecord | ContractCheckin | TrainerSessionPayload,
    *names: str,
) -> WizardValue:
    """Read one canonical field alias without reflective attribute access."""
    if isinstance(source, Event):
        return _mapping_value(
            {
                "event_id": source.event_id,
                "event_type": source.event_type,
                "occurred_at_kst": source.occurred_at_kst,
                "status": source.status,
                "supersedes": source.supersedes,
                "check_in": source.check_in,
                "trainer_session": source.trainer_session,
            },
            names,
        )
    if isinstance(source, ContractCheckin):
        return _mapping_value(
            {
                "body_weight_kg": source.body_weight_kg,
                "weight_kg": source.body_weight_kg,
            },
            names,
        )
    if isinstance(source, TrainerSessionPayload):
        return _mapping_value(
            {
                "performance": source.performance_1to5,
                "performance_1to5": source.performance_1to5,
                "intensity": source.intensity_vs_plan,
                "intensity_vs_plan": source.intensity_vs_plan,
                "pain": source.pain_summary,
                "pain_summary": source.pain_summary,
            },
            names,
        )
    return _mapping_value(source, names)


def _event_records(value: WizardValue) -> TypeGuard[Sequence[EventRecord]]:
    return isinstance(value, Sequence) and not isinstance(value, (str, bytes))


def _records(value: WizardValue) -> tuple[EventRecord, ...]:
    if not _event_records(value):
        return ()
    return tuple(value)


def _text(value: WizardValue) -> str:
    if isinstance(value, EventType):
        return value.value
    return "" if value is None else str(value)


def project_customer_event_state(
    events: ProjectionSource,
    kst_day: str | None = None,
) -> dict[str, WizardValue]:
    """Project prior weight and same-day trainer evidence from canonical events."""
    explicit: Mapping[str, WizardValue] = events if string_mapping(events) else {}
    raw_events = explicit.get("events", explicit.get("canonical_events", ()))
    records = _records(raw_events) if explicit else _records(events)
    if kst_day is None:
        candidate_day = _mapping_value(explicit, ("kst_day", "day", "date"))
        kst_day = str(candidate_day) if candidate_day is not None else None
    state: dict[str, WizardValue] = {}
    prior = _mapping_value(
        explicit,
        (
            "prior_week_same_weekday_weight",
            "prior_week_weight",
            "prior_weight_kg",
            "previous_week_weight",
        ),
    )
    if prior is not None:
        prior_weight = numeric_float(prior)
        if prior_weight is not None:
            state["prior_week_same_weekday_weight"] = prior_weight
    trainer = _mapping_value(explicit, ("trainer_session", "trainer_evidence"))
    try:
        target = date.fromisoformat(kst_day[:10]) if kst_day else None
    except ValueError:
        target = None
    prior_day = (target - timedelta(days=7)).isoformat() if target is not None else None
    superseded = {
        str(event_value(record, "supersedes"))
        for record in records
        if event_value(record, "supersedes") is not None
    }
    if prior_day is not None:
        accepted_weight_types = {
            EventType.CHECK_IN_VALIDATED.value,
            EventType.CORRECTION.value,
            EventType.MORNING_CHECKIN.value,
            EventType.NUTRITION_CHECKIN.value,
        }
        for record in records:
            if str(event_value(record, "event_id") or "") in superseded:
                continue
            status = _text(event_value(record, "status"))
            if status and status not in {"accepted", "ContractStatus.ACCEPTED"}:
                continue
            if _text(event_value(record, "event_type")) not in accepted_weight_types:
                continue
            if str(event_value(record, "occurred_at_kst") or "")[:10] != prior_day:
                continue
            checkin = event_value(record, "check_in")
            if not isinstance(checkin, ContractCheckin) and not string_mapping(checkin):
                continue
            weight = numeric_float(event_value(checkin, "body_weight_kg", "weight_kg"))
            if weight is not None:
                state["prior_week_same_weekday_weight"] = weight
    if trainer is None:
        for record in records:
            if str(event_value(record, "event_id") or "") in superseded:
                continue
            status = _text(event_value(record, "status"))
            if status and status not in {"accepted", "ContractStatus.ACCEPTED"}:
                continue
            if _text(event_value(record, "event_type")) != EventType.TRAINER_SESSION_RECORD.value:
                continue
            occurred = str(event_value(record, "occurred_at_kst") or "")[:10]
            if target is not None and occurred != target.isoformat():
                continue
            trainer = event_value(record, "trainer_session")
    if trainer is not None:
        state["trainer_session"] = trainer
    return state
