"""Adaptive cross-merge recipe selector with local-only canary promotion."""

from __future__ import annotations

import hashlib
import json
import os
import time
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Dict, List, Optional, Sequence, Tuple

import numpy as np

from model_osmosis.optimizer.self_optimize import (
    get_subsystem_metadata,
    load_state,
    optimizer_home,
    resolve_subsystem_mode,
    save_state,
)


CROSS_SELECTOR_VERSION = "cross-merge-mlp-v1"
DEFAULT_CROSS_PACK_VERSION = "builtin-cross-pack-v1"
CROSS_SELECTION_BUDGET_MS = 1800
MIN_SHADOW_RUNS = 3
CANARY_FRACTION = 0.10
PROMOTE_CANARY_PASSES = 5
QUALITY_TOLERANCE = 1e-6


@dataclass(frozen=True)
class CrossMergeSelection:
    """Selected cross-merge recipe + optimizer metadata."""

    strategy_engine_mode: str
    cross_opt_stage: str
    is_llm: bool
    recipe_id: str
    recipe_pack_version: str
    selector_version: str
    selection_latency_ms: float
    fallback_reason: str
    canary_status: str
    rollback_reason: str
    cross_kwargs: Dict[str, Any]
    profile_id: str
    pack_version: str
    optimizer_mode: str

    def to_report_dict(self) -> Dict[str, Any]:
        return {
            "strategy_engine_mode": self.strategy_engine_mode,
            "cross_opt_stage": self.cross_opt_stage,
            "strategy_name": "cross-merge",
            "strategy_kwargs": {
                str(k): v
                for k, v in sorted((self.cross_kwargs or {}).items(), key=lambda item: str(item[0]))
            },
            "recipe_id": self.recipe_id,
            "recipe_pack_version": self.recipe_pack_version,
            "selector_version": self.selector_version,
            "selection_latency_ms": round(float(self.selection_latency_ms), 4),
            "fallback_reason": self.fallback_reason,
            "optimizer_mode": self.optimizer_mode,
            "profile_id": self.profile_id,
            "pack_version": self.pack_version,
            "canary_status": self.canary_status,
            "rollback_reason": self.rollback_reason,
            "is_llm": self.is_llm,
        }


def resolve_cross_opt_stage(stage: Optional[str]) -> str:
    mode = str(stage or "auto").strip().lower()
    if mode not in {"auto", "shadow", "active"}:
        raise ValueError("Unsupported cross-opt-stage. Expected one of: ['active', 'auto', 'shadow']")
    return mode


def _resolve_strategy_mode(requested: Optional[str]) -> str:
    return resolve_subsystem_mode(requested, subsystem_env_var="OSMOSIS_STRATEGY_ENGINE")


def _subsystem_name(is_llm: bool) -> str:
    return "cross_merge_llm" if is_llm else "cross_merge_classic"


def _builtin_cross_pack() -> Dict[str, Any]:
    recipes = [
        {
            "recipe_id": "classic_fast_v1",
            "target": "classic",
            "kwargs": {"common_dim": 192, "projection_epochs": 18, "student_hidden": 384, "student_layers": 2, "lr": 0.0018, "batch_size": 64},
        },
        {
            "recipe_id": "classic_balanced_v1",
            "target": "classic",
            "kwargs": {"common_dim": 256, "projection_epochs": 35, "student_hidden": 512, "student_layers": 3, "lr": 0.001, "batch_size": 32},
        },
        {
            "recipe_id": "classic_quality_v1",
            "target": "classic",
            "kwargs": {"common_dim": 320, "projection_epochs": 50, "student_hidden": 640, "student_layers": 4, "lr": 0.0007, "batch_size": 24},
        },
        {
            "recipe_id": "llm_fast_v1",
            "target": "llm",
            "kwargs": {"common_dim": 192, "distillation_epochs": 2, "learning_rate": 3.5e-5, "batch_size": 6, "gradient_accumulation_steps": 4, "projection_epochs": 20, "lm_loss_weight": 0.08, "use_lora": True},
        },
        {
            "recipe_id": "llm_balanced_v1",
            "target": "llm",
            "kwargs": {"common_dim": 256, "distillation_epochs": 3, "learning_rate": 2.0e-5, "batch_size": 4, "gradient_accumulation_steps": 8, "projection_epochs": 35, "lm_loss_weight": 0.10, "use_lora": True},
        },
        {
            "recipe_id": "llm_quality_v1",
            "target": "llm",
            "kwargs": {"common_dim": 320, "distillation_epochs": 4, "learning_rate": 1.6e-5, "batch_size": 3, "gradient_accumulation_steps": 10, "projection_epochs": 50, "lm_loss_weight": 0.12, "use_lora": True},
        },
    ]
    payload = {
        "pack_version": DEFAULT_CROSS_PACK_VERSION,
        "selector_version": CROSS_SELECTOR_VERSION,
        "recipes": recipes,
    }
    payload["checksum_sha256"] = hashlib.sha256(
        json.dumps(payload, sort_keys=True, separators=(",", ":")).encode("utf-8")
    ).hexdigest()
    return payload


def _cross_pack_path() -> Optional[Path]:
    env_path = str(os.environ.get("OSMOSIS_CROSS_PACK", "")).strip()
    if env_path:
        return Path(env_path)
    default = optimizer_home() / "packs" / "cross_merge_pack.json"
    return default if default.exists() else None


def _load_cross_pack() -> Tuple[Dict[str, Any], str]:
    path = _cross_pack_path()
    if path is None:
        return _builtin_cross_pack(), "builtin_pack"

    try:
        data = json.loads(path.read_text(encoding="utf-8"))
    except Exception:
        return _builtin_cross_pack(), "invalid_pack_json_fallback_builtin"
    if not isinstance(data, dict):
        return _builtin_cross_pack(), "invalid_pack_schema_fallback_builtin"

    payload = {
        "pack_version": data.get("pack_version", ""),
        "selector_version": data.get("selector_version", ""),
        "recipes": data.get("recipes", []),
    }
    encoded = json.dumps(payload, sort_keys=True, separators=(",", ":")).encode("utf-8")
    expected = str(data.get("checksum_sha256", "")).strip().lower()
    computed = hashlib.sha256(encoded).hexdigest()
    if expected and expected != computed:
        return _builtin_cross_pack(), "pack_checksum_mismatch_fallback_builtin"
    if not expected:
        data["checksum_sha256"] = computed

    trust_block = data.get("trust")
    if isinstance(trust_block, dict):
        try:
            from model_osmosis.format.trust import verify_trust_block_with_backend

            verify = verify_trust_block_with_backend(
                trust_block=trust_block,
                payload_bytes=encoded,
                backend_name=str(trust_block.get("backend") or ""),
            )
            status = str(verify.get("trust_status", "unsigned"))
            if status not in {"trusted", "signed_untrusted"}:
                return _builtin_cross_pack(), "pack_signature_invalid_fallback_builtin"
        except Exception:
            return _builtin_cross_pack(), "pack_signature_invalid_fallback_builtin"

    return data, "external_pack"


def _is_llm_cross(models: Sequence[Any]) -> bool:
    if not models:
        return False
    first = models[0]
    if isinstance(first, str):
        return True
    if hasattr(first, "config") and hasattr(getattr(first, "config"), "_name_or_path"):
        return True
    return False


def _extract_context_features(
    *,
    models: Sequence[Any],
    sample_data: Any,
    weights: Optional[Sequence[float]],
    is_llm: bool,
    epochs: int,
) -> Dict[str, float]:
    model_count = float(len(models))
    weight_var = 0.0
    if weights:
        arr = np.asarray([float(w) for w in weights], dtype=np.float64)
        if arr.size > 0:
            arr = arr / max(1e-12, float(arr.sum()))
            weight_var = float(np.var(arr))

    if is_llm:
        texts = sample_data if isinstance(sample_data, list) else []
        lengths = [len(str(t)) for t in texts]
        sample_count = float(len(lengths))
        mean_len = float(np.mean(lengths)) if lengths else 0.0
        std_len = float(np.std(lengths)) if lengths else 0.0
        return {
            "model_count_norm": min(1.0, model_count / 8.0),
            "sample_count_norm": min(1.0, sample_count / 128.0),
            "mean_len_log": float(np.log10(max(1.0, mean_len))),
            "std_len_log": float(np.log10(max(1.0, std_len + 1.0))),
            "weight_var": min(1.0, weight_var * 10.0),
            "epochs_norm": min(1.0, float(max(1, epochs)) / 10.0),
            "is_llm": 1.0,
            "bias": 1.0,
        }

    if isinstance(sample_data, np.ndarray):
        arr = np.asarray(sample_data, dtype=np.float64)
        sample_count = float(arr.shape[0]) if arr.ndim > 0 else 1.0
        feature_dim = float(arr.shape[-1]) if arr.ndim > 0 else 1.0
        mean_abs = float(np.mean(np.abs(arr))) if arr.size > 0 else 0.0
        std_val = float(np.std(arr)) if arr.size > 0 else 0.0
    else:
        sample_count = 0.0
        feature_dim = 0.0
        mean_abs = 0.0
        std_val = 0.0
    return {
        "model_count_norm": min(1.0, model_count / 8.0),
        "sample_count_norm": min(1.0, sample_count / 2048.0),
        "feature_dim_log": float(np.log10(max(1.0, feature_dim))),
        "mean_abs_log": float(np.log10(max(1e-9, mean_abs)) + 9.0),
        "std_log": float(np.log10(max(1e-9, std_val)) + 9.0),
        "weight_var": min(1.0, weight_var * 10.0),
        "epochs_norm": min(1.0, float(max(1, epochs)) / 200.0),
        "is_llm": 0.0,
        "bias": 1.0,
    }


def _recipes_from_pack(pack: Dict[str, Any], *, is_llm: bool) -> List[Dict[str, Any]]:
    target = "llm" if is_llm else "classic"
    raw = pack.get("recipes", [])
    recipes: List[Dict[str, Any]] = []
    if not isinstance(raw, list):
        return recipes
    for item in raw:
        if not isinstance(item, dict):
            continue
        recipe_id = str(item.get("recipe_id", "")).strip()
        recipe_target = str(item.get("target", "")).strip().lower()
        kwargs = item.get("kwargs", {})
        if not recipe_id or recipe_target != target or not isinstance(kwargs, dict):
            continue
        recipes.append(
            {
                "recipe_id": recipe_id,
                "kwargs": dict(kwargs),
            }
        )
    return sorted(recipes, key=lambda rec: rec["recipe_id"])


def _score_recipes(
    *,
    recipes: Sequence[Dict[str, Any]],
    context: Dict[str, float],
    subsystem_metadata: Dict[str, Any],
) -> Dict[str, float]:
    if context.get("is_llm", 0.0) >= 0.5:
        feature_vec = np.array(
            [
                context["model_count_norm"],
                context["sample_count_norm"],
                context["mean_len_log"],
                context["std_len_log"],
                context["weight_var"],
                context["epochs_norm"],
                context["is_llm"],
                context["bias"],
            ],
            dtype=np.float64,
        )
    else:
        feature_vec = np.array(
            [
                context["model_count_norm"],
                context["sample_count_norm"],
                context["feature_dim_log"],
                context["mean_abs_log"],
                context["std_log"],
                context["weight_var"],
                context["epochs_norm"],
                context["bias"],
            ],
            dtype=np.float64,
        )

    w1 = np.array(
        [
            [0.22, -0.11, 0.15, 0.19, -0.07, 0.08],
            [0.12, 0.28, -0.19, 0.14, 0.11, -0.04],
            [-0.09, 0.17, 0.24, -0.08, 0.05, 0.16],
            [0.16, -0.06, 0.21, 0.12, -0.03, 0.11],
            [0.07, 0.09, -0.05, 0.18, 0.25, 0.08],
            [0.10, -0.08, 0.14, 0.07, 0.19, 0.2],
            [0.09, 0.05, 0.08, 0.06, 0.04, 0.05],
            [0.06, 0.06, 0.06, 0.06, 0.06, 0.06],
        ],
        dtype=np.float64,
    )
    b1 = np.array([0.01, -0.02, 0.03, 0.0, 0.02, 0.01], dtype=np.float64)
    hidden = np.tanh(feature_vec @ w1 + b1)
    w2 = np.array([0.31, 0.19, 0.24, 0.22, 0.27, 0.28], dtype=np.float64)
    base = float(hidden @ w2)

    scores: Dict[str, float] = {}
    prior_scores = subsystem_metadata.get("scores", {}) if isinstance(subsystem_metadata, dict) else {}
    for rec in recipes:
        rid = str(rec["recipe_id"])
        kwargs = rec.get("kwargs", {})
        score = base + _recipe_param_bonus(rid, kwargs, context)
        score += float(prior_scores.get(rid, 0.0) or 0.0)
        scores[rid] = float(score)
    return scores


def _recipe_param_bonus(recipe_id: str, kwargs: Dict[str, Any], context: Dict[str, float]) -> float:
    rid = str(recipe_id)
    epochs_norm = float(context.get("epochs_norm", 0.0))
    if "fast" in rid:
        return 0.12 - 0.10 * epochs_norm
    if "quality" in rid:
        return 0.08 + 0.15 * epochs_norm
    common_dim = float(kwargs.get("common_dim", 256) or 256)
    if context.get("is_llm", 0.0) >= 0.5:
        target = 256.0 if context.get("sample_count_norm", 0.0) < 0.2 else 320.0
    else:
        target = 256.0 if context.get("feature_dim_log", 0.0) < 2.0 else 320.0
    return 0.15 - abs(common_dim - target) / 512.0


def _proxy_quality(recipe: Dict[str, Any], context: Dict[str, float]) -> float:
    kwargs = recipe.get("kwargs", {})
    common_dim = float(kwargs.get("common_dim", 256) or 256)
    dim_score = 1.0 - abs(common_dim - 256.0) / 256.0
    if context.get("is_llm", 0.0) >= 0.5:
        lr = float(kwargs.get("learning_rate", 2e-5) or 2e-5)
        epoch = float(kwargs.get("distillation_epochs", 3) or 3)
        lr_score = 1.0 - min(1.0, abs(np.log10(max(lr, 1e-8)) - np.log10(2e-5)) / 2.0)
        epoch_score = 1.0 - min(1.0, abs(epoch - 3.0) / 6.0)
        return 0.45 * dim_score + 0.35 * lr_score + 0.20 * epoch_score
    lr = float(kwargs.get("lr", 1e-3) or 1e-3)
    epoch = float(kwargs.get("projection_epochs", 35) or 35)
    lr_score = 1.0 - min(1.0, abs(np.log10(max(lr, 1e-9)) - np.log10(1e-3)) / 2.0)
    epoch_score = 1.0 - min(1.0, abs(epoch - 35.0) / 70.0)
    return 0.45 * dim_score + 0.35 * lr_score + 0.20 * epoch_score


def _context_fingerprint(
    *,
    context: Dict[str, float],
    source_refs: Sequence[str],
    seed: int,
    pack_version: str,
    selector_version: str,
) -> str:
    payload = {
        "context": {k: float(v) for k, v in sorted(context.items(), key=lambda item: item[0])},
        "source_refs": sorted(str(ref) for ref in source_refs),
        "seed": int(seed),
        "pack_version": str(pack_version),
        "selector_version": str(selector_version),
    }
    data = json.dumps(payload, sort_keys=True, separators=(",", ":")).encode("utf-8")
    return hashlib.sha256(data).hexdigest()


def _is_canary_eligible(selection_key: str, run_idx: int) -> bool:
    digest = hashlib.sha256(f"{selection_key}:{run_idx}".encode("utf-8")).hexdigest()
    value = int(digest[:8], 16) / float(0xFFFFFFFF)
    return value < CANARY_FRACTION


def _fallback_selection(
    *,
    strategy_mode: str,
    cross_opt_stage: str,
    is_llm: bool,
    fallback_reason: str,
    recipe_id: str,
    recipe_kwargs: Dict[str, Any],
    pack_version: str = DEFAULT_CROSS_PACK_VERSION,
) -> CrossMergeSelection:
    subsystem = _subsystem_name(is_llm)
    metadata = get_subsystem_metadata(subsystem)
    return CrossMergeSelection(
        strategy_engine_mode=strategy_mode,
        cross_opt_stage=cross_opt_stage,
        is_llm=is_llm,
        recipe_id=recipe_id,
        recipe_pack_version=pack_version,
        selector_version=CROSS_SELECTOR_VERSION,
        selection_latency_ms=0.0,
        fallback_reason=fallback_reason,
        canary_status=str(metadata.get("canary_status", "shadow")),
        rollback_reason=str(metadata.get("rollback_reason", "")),
        cross_kwargs=dict(recipe_kwargs),
        profile_id=recipe_id,
        pack_version=pack_version,
        optimizer_mode=strategy_mode,
    )


def select_cross_merge_recipe(
    *,
    models: Sequence[Any],
    sample_data: Any,
    weights: Optional[Sequence[float]] = None,
    seed: int = 42,
    strategy_engine_mode: str = "auto",
    cross_opt_stage: str = "auto",
    source_refs: Optional[Sequence[str]] = None,
    epochs: int = 3,
    budget_ms: int = CROSS_SELECTION_BUDGET_MS,
) -> CrossMergeSelection:
    """Select a cross-merge recipe with local canary+rollback policy."""
    started = time.perf_counter()
    stage = resolve_cross_opt_stage(cross_opt_stage)
    mode = _resolve_strategy_mode(strategy_engine_mode)
    is_llm = _is_llm_cross(models)

    baseline_recipe_id = "llm_balanced_v1" if is_llm else "classic_balanced_v1"
    baseline_kwargs = {
        "common_dim": 256,
    }
    if is_llm:
        baseline_kwargs.update(
            {
                "distillation_epochs": 3,
                "learning_rate": 2.0e-5,
                "batch_size": 4,
                "gradient_accumulation_steps": 8,
                "projection_epochs": 35,
                "lm_loss_weight": 0.10,
                "use_lora": True,
            }
        )
    else:
        baseline_kwargs.update(
            {
                "projection_epochs": 35,
                "student_hidden": 512,
                "student_layers": 3,
                "lr": 0.001,
                "batch_size": 32,
            }
        )

    if mode == "off":
        return _fallback_selection(
            strategy_mode=mode,
            cross_opt_stage=stage,
            is_llm=is_llm,
            fallback_reason="strategy_engine_off",
            recipe_id=baseline_recipe_id,
            recipe_kwargs=baseline_kwargs,
        )

    pack, pack_reason = _load_cross_pack()
    recipes = _recipes_from_pack(pack, is_llm=is_llm)
    if not recipes:
        return _fallback_selection(
            strategy_mode=mode,
            cross_opt_stage=stage,
            is_llm=is_llm,
            fallback_reason="no_valid_recipes",
            recipe_id=baseline_recipe_id,
            recipe_kwargs=baseline_kwargs,
            pack_version=str(pack.get("pack_version") or DEFAULT_CROSS_PACK_VERSION),
        )

    context = _extract_context_features(
        models=models,
        sample_data=sample_data,
        weights=weights,
        is_llm=is_llm,
        epochs=epochs,
    )
    subsystem = _subsystem_name(is_llm)
    state = load_state()
    entry = state.setdefault("subsystems", {}).setdefault(
        subsystem,
        {
            "canary_status": "shadow",
            "promoted_profile_id": "",
            "active_profile_id": "",
            "rollback_reason": "",
            "pack_version": "",
            "selector_version": "",
            "last_selection_latency_ms": 0.0,
            "runs": 0,
            "shadow_runs": 0,
            "canary_attempts": 0,
            "canary_passes": 0,
            "candidates": {},
            "scores": {},
        },
    )

    scores = _score_recipes(recipes=recipes, context=context, subsystem_metadata=entry)
    ranked = sorted(scores.items(), key=lambda item: (-float(item[1]), str(item[0])))
    recipe_map = {str(r["recipe_id"]): r for r in recipes}
    top_id = ranked[0][0]
    top_recipe = recipe_map[top_id]

    baseline_recipe = recipe_map.get(baseline_recipe_id, {"recipe_id": baseline_recipe_id, "kwargs": baseline_kwargs})
    baseline_quality = _proxy_quality(baseline_recipe, context)
    candidate_quality = _proxy_quality(top_recipe, context)
    quality_ok = candidate_quality + QUALITY_TOLERANCE >= baseline_quality

    refs = [str(r) for r in (source_refs or [])]
    selection_key = _context_fingerprint(
        context=context,
        source_refs=refs,
        seed=seed,
        pack_version=str(pack.get("pack_version") or DEFAULT_CROSS_PACK_VERSION),
        selector_version=str(pack.get("selector_version") or CROSS_SELECTOR_VERSION),
    )

    entry["runs"] = int(entry.get("runs", 0) or 0) + 1
    run_idx = int(entry["runs"])
    promoted_profile = str(entry.get("promoted_profile_id", "") or "")

    if stage == "shadow":
        stage_mode = "shadow"
    elif stage == "active":
        stage_mode = "canary"
    else:
        if not promoted_profile and int(entry.get("shadow_runs", 0) or 0) < MIN_SHADOW_RUNS:
            stage_mode = "shadow"
        elif _is_canary_eligible(selection_key, run_idx):
            stage_mode = "canary"
        else:
            stage_mode = "active" if promoted_profile else "shadow"

    fallback_reason = pack_reason
    used_recipe = top_recipe
    canary_status = "shadow"
    rollback_reason = ""

    if stage_mode == "shadow":
        entry["shadow_runs"] = int(entry.get("shadow_runs", 0) or 0) + 1
        if promoted_profile and promoted_profile in recipe_map:
            used_recipe = recipe_map[promoted_profile]
            canary_status = "promoted"
        else:
            used_recipe = baseline_recipe
            canary_status = "shadow"
        fallback_reason = "shadow_stage_baseline_or_promoted"
    elif stage_mode == "active":
        if promoted_profile and promoted_profile in recipe_map:
            used_recipe = recipe_map[promoted_profile]
            canary_status = "promoted"
        else:
            used_recipe = baseline_recipe
            canary_status = "shadow"
            fallback_reason = "no_promoted_profile_baseline"
    else:
        entry["canary_attempts"] = int(entry.get("canary_attempts", 0) or 0) + 1
        if not quality_ok:
            used_recipe = recipe_map.get(promoted_profile, baseline_recipe)
            canary_status = "rollback"
            rollback_reason = "quality_regression_blocked"
            entry["canary_passes"] = 0
            entry["rollback_reason"] = rollback_reason
            state["rollback_marker"] = {
                "subsystem": subsystem,
                "profile_id": str(top_recipe["recipe_id"]),
                "reason": rollback_reason,
                "at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
            }
            fallback_reason = rollback_reason
        else:
            entry["canary_passes"] = int(entry.get("canary_passes", 0) or 0) + 1
            used_recipe = top_recipe
            canary_status = "canary"
            if int(entry["canary_passes"]) >= PROMOTE_CANARY_PASSES:
                entry["promoted_profile_id"] = str(top_recipe["recipe_id"])
                canary_status = "promoted"
                entry["rollback_reason"] = ""

    used_recipe_id = str(used_recipe["recipe_id"])
    entry["active_profile_id"] = used_recipe_id
    entry["pack_version"] = str(pack.get("pack_version") or DEFAULT_CROSS_PACK_VERSION)
    entry["selector_version"] = str(pack.get("selector_version") or CROSS_SELECTOR_VERSION)
    entry["last_selection_latency_ms"] = round((time.perf_counter() - started) * 1000.0, 4)
    entry["canary_status"] = canary_status
    if rollback_reason:
        entry["rollback_reason"] = rollback_reason
    elif canary_status != "rollback":
        entry["rollback_reason"] = ""

    candidates = entry.setdefault("candidates", {})
    candidates[top_id] = int(candidates.get(top_id, 0) or 0) + 1
    scores_map = entry.setdefault("scores", {})
    prev = float(scores_map.get(top_id, 0.0) or 0.0)
    scores_map[top_id] = round(0.8 * prev + 0.2 * float(scores[top_id]), 6)

    elapsed_ms = (time.perf_counter() - started) * 1000.0
    if elapsed_ms > float(budget_ms):
        save_state(state)
        return _fallback_selection(
            strategy_mode=mode,
            cross_opt_stage=stage,
            is_llm=is_llm,
            fallback_reason="selection_budget_exceeded",
            recipe_id=baseline_recipe_id,
            recipe_kwargs=baseline_kwargs,
            pack_version=str(pack.get("pack_version") or DEFAULT_CROSS_PACK_VERSION),
        )

    save_state(state)
    metadata = get_subsystem_metadata(subsystem)
    return CrossMergeSelection(
        strategy_engine_mode=mode,
        cross_opt_stage=stage,
        is_llm=is_llm,
        recipe_id=used_recipe_id,
        recipe_pack_version=str(pack.get("pack_version") or DEFAULT_CROSS_PACK_VERSION),
        selector_version=str(pack.get("selector_version") or CROSS_SELECTOR_VERSION),
        selection_latency_ms=elapsed_ms,
        fallback_reason=fallback_reason,
        canary_status=str(metadata.get("canary_status", canary_status)),
        rollback_reason=str(metadata.get("rollback_reason", rollback_reason)),
        cross_kwargs=dict(used_recipe.get("kwargs", {})),
        profile_id=used_recipe_id,
        pack_version=str(pack.get("pack_version") or DEFAULT_CROSS_PACK_VERSION),
        optimizer_mode=mode,
    )
