from __future__ import annotations

from collections import defaultdict
from datetime import datetime
from typing import Any

from django.utils import timezone


def safe_number(value, default=0):
    return default if value is None else value


SEVERITY_ORDER = {"low": 1, "medium": 2, "high": 3, "critical": 4}
SEVERITY_UI = {
    "low": {"avatarColor": "info", "chipColor": "info"},
    "medium": {"avatarColor": "warning", "chipColor": "warning"},
    "high": {"avatarColor": "error", "chipColor": "error"},
    "critical": {"avatarColor": "error", "chipColor": "error"},
}
METRIC_META = {
    "moisture": {
        "title": "تنش رطوبتی",
        "icon": "tabler-droplet-half-2",
        "unit": "%",
        "domain": "water_balance",
        "threshold": 45.0,
        "danger_span": 20.0,
        "direction": "below",
    },
    "temperature": {
        "title": "تنش دمایی",
        "icon": "tabler-snowflake",
        "unit": "°C",
        "domain": "temperature_stress",
        "threshold": 0.0,
        "danger_span": 8.0,
        "direction": "below",
    },
    "ph": {
        "title": "عدم تعادل pH",
        "icon": "tabler-flask",
        "unit": "pH",
        "domain": "root_chemistry",
        "threshold_low": 6.0,
        "threshold_high": 7.5,
        "danger_span": 1.5,
    },
    "ec": {
        "title": "شوری / EC بالا",
        "icon": "tabler-bolt",
        "unit": "dS/m",
        "domain": "root_chemistry",
        "threshold": 3.0,
        "danger_span": 2.0,
        "direction": "above",
    },
    "fungal_risk": {
        "title": "ریسک قارچی",
        "icon": "tabler-mushroom",
        "unit": "%",
        "domain": "disease_pressure",
        "threshold": 70.0,
        "danger_span": 20.0,
        "direction": "above",
    },
}

SUMMARY_TEMPLATES = {
    "moisture": {
        "low": "افت رطوبت خاک ثبت شده و نیاز به پایش نزدیک‌تر دارد.",
        "medium": "رطوبت خاک پایین‌تر از محدوده مطلوب است و برنامه آبیاری باید بازبینی شود.",
        "high": "تنش آبی قابل‌توجه شناسایی شده و مزرعه به اقدام آبیاری سریع نیاز دارد.",
        "critical": "کمبود شدید رطوبت فعال است و خطر افت رشد یا آسیب ریشه بالا رفته است.",
    },
    "temperature": {
        "low": "دمای پایین ثبت شده و باید روند شبانه پایش شود.",
        "medium": "ریسک سرمازدگی ایجاد شده و اقدامات محافظتی باید آماده شود.",
        "high": "سرما به محدوده پرخطر رسیده و حفاظت دمایی باید در اولویت باشد.",
        "critical": "یخبندان بحرانی پیش‌بینی یا مشاهده شده و اقدام فوری حفاظتی لازم است.",
    },
    "ph": {
        "low": "pH از محدوده مطلوب فاصله گرفته و نیاز به بررسی اصلاحی دارد.",
        "medium": "عدم تعادل pH می‌تواند جذب عناصر را مختل کند و باید اصلاح شود.",
        "high": "انحراف pH شدید است و ریسک اختلال تغذیه گیاه بالا رفته است.",
        "critical": "pH در وضعیت بحرانی قرار دارد و مداخله سریع برای جلوگیری از تنش تغذیه‌ای لازم است.",
    },
    "ec": {
        "low": "EC بالاتر از حد مرجع است و باید روند شوری پیگیری شود.",
        "medium": "شوری خاک می‌تواند رشد را محدود کند و نیاز به تعدیل دارد.",
        "high": "EC بالا به سطح پرخطر رسیده و مدیریت شوری باید انجام شود.",
        "critical": "شوری بحرانی فعال است و احتمال آسیب ریشه و افت جذب آب بسیار بالاست.",
    },
    "fungal_risk": {
        "low": "شرایط اولیه برای فشار بیماری قارچی مشاهده شده است.",
        "medium": "رطوبت و خیس‌ماندگی بستر، ریسک بیماری قارچی را افزایش داده است.",
        "high": "فشار بیماری قارچی بالا است و عملیات پیشگیرانه باید در اولویت قرار گیرد.",
        "critical": "الگوی بسیار پرخطر بیماری قارچی فعال است و اقدام فوری محافظتی لازم است.",
    },
}

ACTION_TEMPLATES = {
    "moisture": {
        "low": "روند رطوبت را در نوبت بعدی کنترل و یکنواختی آبیاری را بررسی کنید.",
        "medium": "یک نوبت آبیاری اصلاحی برنامه‌ریزی و افت رطوبت در عمق‌های مختلف پایش شود.",
        "high": "آبیاری جبرانی کوتاه‌مدت اجرا و راندمان روش آبیاری بازبینی شود.",
        "critical": "آبیاری اضطراری، بررسی انسداد سامانه و پایش مجدد سنسور فوراً انجام شود.",
    },
    "temperature": {
        "low": "پوشش یا برنامه محافظتی شبانه آماده نگه داشته شود.",
        "medium": "زمان‌بندی آبیاری و پوشش حفاظتی برای ساعات سرد تنظیم شود.",
        "high": "اقدامات ضدیخبندان مانند آبیاری حفاظتی یا پوشش فوری اجرا شود.",
        "critical": "پروتکل کامل حفاظت سرما فوراً فعال و وضعیت مزرعه در چند ساعت بعدی بازبینی شود.",
    },
    "ph": {
        "low": "نمونه‌برداری تکمیلی انجام و روند pH برای چند قرائت بعدی کنترل شود.",
        "medium": "برنامه اصلاح pH با توجه به نوع خاک و کود مصرفی بازتنظیم شود.",
        "high": "اصلاح‌کننده مناسب خاک در اولویت قرار گیرد و تغذیه گیاه بازبینی شود.",
        "critical": "مداخله اصلاحی فوری برای pH انجام و مصرف نهاده‌های تشدیدکننده متوقف شود.",
    },
    "ec": {
        "low": "منبع آب و روند EC در روزهای آینده کنترل شود.",
        "medium": "شست‌وشوی محدود خاک یا اصلاح برنامه کوددهی بررسی شود.",
        "high": "کاهش بار نمکی، بازبینی کوددهی و ارزیابی زهکشی در اولویت قرار گیرد.",
        "critical": "اقدام فوری برای کاهش شوری و توقف نهاده‌های شورکننده انجام شود.",
    },
    "fungal_risk": {
        "low": "تهویه و رطوبت بستر پایش شود و نشانه‌های اولیه بیماری بررسی گردد.",
        "medium": "فاصله آبیاری و تهویه مزرعه تنظیم و بازدید بیماری انجام شود.",
        "high": "اقدامات پیشگیرانه بیماری و کاهش رطوبت ماندگار فوراً اجرا شود.",
        "critical": "پروتکل فوری مدیریت بیماری فعال و مزرعه از نظر آلودگی کانونی بررسی شود.",
    },
}

EXPLANATION_TEMPLATES = {
    "moisture": {
        "low": "رطوبت فعلی {current_value}{unit} به زیر آستانه {threshold_value}{unit} رسیده است و این وضعیت {duration_text} ادامه داشته است.",
        "medium": "رطوبت خاک {current_value}{unit} است؛ فاصله از آستانه {threshold_value}{unit} و تداوم {duration_text} نشان‌دهنده تنش آبی است.",
        "high": "رطوبت خاک در {current_value}{unit} ثبت شده که به‌طور معنی‌دار پایین‌تر از آستانه {threshold_value}{unit} است و {duration_text} پایدار مانده است.",
        "critical": "رطوبت خاک به {current_value}{unit} سقوط کرده و با عبور شدید از آستانه {threshold_value}{unit}، {duration_text} در وضعیت بحرانی باقی مانده است.",
    },
    "temperature": {
        "low": "دما به {current_value}{unit} رسیده که از حد هشدار {threshold_value}{unit} پایین‌تر است و {duration_text} تداوم داشته است.",
        "medium": "دمای ثبت‌شده {current_value}{unit} کمتر از آستانه {threshold_value}{unit} است و تداوم {duration_text} ریسک تنش سرما را بالا برده است.",
        "high": "افت دما تا {current_value}{unit} همراه با ماندگاری {duration_text} شرایط پرخطر سرما را ایجاد کرده است.",
        "critical": "دمای {current_value}{unit} با ماندگاری {duration_text} نشان می‌دهد مزرعه در معرض یخبندان بحرانی قرار دارد.",
    },
    "ph": {
        "low": "pH فعلی {current_value}{unit} از محدوده مرجع {threshold_value} خارج شده و این انحراف {duration_text} ادامه داشته است.",
        "medium": "انحراف pH تا {current_value}{unit} نسبت به حد مجاز {threshold_value} همراه با تداوم {duration_text} می‌تواند جذب عناصر را مختل کند.",
        "high": "pH {current_value}{unit} با فاصله زیاد از محدوده مرجع و پایداری {duration_text} یک تنش شیمیایی مهم ایجاد کرده است.",
        "critical": "وضعیت بحرانی pH در سطح {current_value}{unit} و با تداوم {duration_text} نیاز به اصلاح فوری دارد.",
    },
    "ec": {
        "low": "EC فعلی {current_value}{unit} از آستانه {threshold_value}{unit} عبور کرده و {duration_text} پایدار مانده است.",
        "medium": "EC برابر {current_value}{unit} است؛ عبور از حد {threshold_value}{unit} با ماندگاری {duration_text} فشار شوری را افزایش داده است.",
        "high": "شوری ثبت‌شده در {current_value}{unit} با تداوم {duration_text} به سطح پرخطر رسیده است.",
        "critical": "EC در {current_value}{unit} و با پایداری {duration_text} نشان‌دهنده شوری بحرانی خاک است.",
    },
    "fungal_risk": {
        "low": "رطوبت هوا و خاک شرایط اولیه فشار قارچی را ایجاد کرده و این الگو {duration_text} ادامه داشته است.",
        "medium": "ترکیب رطوبت {current_value}{unit} و ماندگاری {duration_text} از آستانه {threshold_value}{unit} عبور کرده و ریسک قارچی را بالا برده است.",
        "high": "شرایط مرطوب پایدار در {current_value}{unit} و تداوم {duration_text} فشار قارچی جدی ایجاد کرده است.",
        "critical": "ماندگاری طولانی شرایط بسیار مرطوب ({current_value}{unit}) در برابر حد {threshold_value}{unit} نشان‌دهنده ریسک بحرانی بیماری قارچی است.",
    },
}

CLUSTER_TITLES = {
    "water_balance": "تعادل آب",
    "temperature_stress": "تنش دمایی",
    "root_chemistry": "شیمی ناحیه ریشه",
    "disease_pressure": "فشار بیماری",
}


def _now() -> datetime:
    return timezone.now()


def _timestamp_for(obj: Any, fallback: datetime) -> datetime:
    for attr in ("recorded_at", "updated_at", "created_at", "forecast_date"):
        value = getattr(obj, attr, None)
        if value is not None:
            if isinstance(value, datetime):
                return value
            return datetime.combine(value, datetime.min.time(), tzinfo=fallback.tzinfo)
    return fallback


def _format_timestamp(value: datetime) -> str:
    if timezone.is_naive(value):
        value = timezone.make_aware(value, timezone.get_current_timezone())
    return value.isoformat()


def _format_duration(hours: float) -> str:
    rounded = max(1, round(hours))
    if rounded >= 24:
        days = rounded // 24
        rem_hours = rounded % 24
        if rem_hours == 0:
            return f"{days} روز"
        return f"{days} روز و {rem_hours} ساعت"
    return f"{rounded} ساعت"


def _severity_from_score(score: float) -> str:
    if score >= 0.85:
        return "critical"
    if score >= 0.55:
        return "high"
    if score >= 0.3:
        return "medium"
    return "low"


def _build_severity(distance_ratio: float, duration_hours: float) -> str:
    duration_ratio = min(duration_hours / 72.0, 1.0)
    score = min((distance_ratio * 0.7) + (duration_ratio * 0.3), 1.0)
    return _severity_from_score(score)


def _collect_active_history_duration(
    current_value: float,
    history: list[Any],
    field_name: str,
    threshold: float,
    direction: str,
    fallback_timestamp: datetime,
) -> tuple[float, datetime]:
    if direction == "below":
        is_violating = lambda value: value < threshold
    else:
        is_violating = lambda value: value > threshold

    if not is_violating(current_value):
        return 0.0, fallback_timestamp

    violating_times = [fallback_timestamp]
    for item in history:
        value = getattr(item, field_name, None)
        if value is None:
            break
        if not is_violating(value):
            break
        violating_times.append(_timestamp_for(item, fallback_timestamp))

    oldest_violation = min(violating_times)
    duration_hours = max((_now() - oldest_violation).total_seconds() / 3600, 1.0)
    return duration_hours, oldest_violation


def _make_alert(
    metric_type: str,
    current_value: float,
    threshold_value: float | str,
    severity: str,
    duration_hours: float,
    timestamp: datetime,
    sensor_id: str,
    zone_id: str | None = None,
    direction: str | None = None,
    metadata: dict[str, Any] | None = None,
) -> dict[str, Any]:
    meta = METRIC_META[metric_type]
    unit = meta["unit"]
    threshold_display = threshold_value
    if isinstance(threshold_value, float):
        threshold_display = round(threshold_value, 2)

    explanation = EXPLANATION_TEMPLATES[metric_type][severity].format(
        current_value=round(current_value, 2),
        threshold_value=threshold_display,
        unit=unit,
        duration_text=_format_duration(duration_hours),
    )
    return {
        "metric_type": metric_type,
        "title": meta["title"],
        "current_value": round(current_value, 2),
        "threshold_value": threshold_display,
        "severity": severity,
        "duration_hours": round(duration_hours, 1),
        "duration": _format_duration(duration_hours),
        "timestamp": _format_timestamp(timestamp),
        "sensor_id": sensor_id,
        "zone_id": zone_id,
        "domain": meta["domain"],
        "direction": direction,
        "unit": unit,
        "icon": meta["icon"],
        "summary": SUMMARY_TEMPLATES[metric_type][severity],
        "recommended_action": ACTION_TEMPLATES[metric_type][severity],
        "explanation": explanation,
        "metadata": metadata or {},
    }


def _detect_moisture_alert(sensor: Any, history: list[Any], sensor_id: str) -> list[dict[str, Any]]:
    current_value = safe_number(getattr(sensor, "soil_moisture", None), 0)
    meta = METRIC_META["moisture"]
    threshold = meta["threshold"]
    if current_value >= threshold:
        return []

    timestamp = _timestamp_for(sensor, _now())
    duration_hours, started_at = _collect_active_history_duration(
        current_value=current_value,
        history=history,
        field_name="soil_moisture",
        threshold=threshold,
        direction=meta["direction"],
        fallback_timestamp=timestamp,
    )
    distance_ratio = min((threshold - current_value) / meta["danger_span"], 1.0)
    severity = _build_severity(distance_ratio, duration_hours)
    return [
        _make_alert(
            metric_type="moisture",
            current_value=current_value,
            threshold_value=threshold,
            severity=severity,
            duration_hours=duration_hours,
            timestamp=started_at,
            sensor_id=sensor_id,
            direction=meta["direction"],
        )
    ]


def _detect_ph_alert(sensor: Any, history: list[Any], sensor_id: str) -> list[dict[str, Any]]:
    current_value = safe_number(getattr(sensor, "soil_ph", None), 7)
    meta = METRIC_META["ph"]
    low = meta["threshold_low"]
    high = meta["threshold_high"]
    if low <= current_value <= high:
        return []

    direction = "below" if current_value < low else "above"
    threshold = low if direction == "below" else high
    timestamp = _timestamp_for(sensor, _now())
    duration_hours, started_at = _collect_active_history_duration(
        current_value=current_value,
        history=history,
        field_name="soil_ph",
        threshold=threshold,
        direction=direction,
        fallback_timestamp=timestamp,
    )
    distance_ratio = min(abs(current_value - threshold) / meta["danger_span"], 1.0)
    severity = _build_severity(distance_ratio, duration_hours)
    threshold_display = f"{low}-{high}"
    return [
        _make_alert(
            metric_type="ph",
            current_value=current_value,
            threshold_value=threshold_display,
            severity=severity,
            duration_hours=duration_hours,
            timestamp=started_at,
            sensor_id=sensor_id,
            direction=direction,
            metadata={"boundary_threshold": threshold},
        )
    ]


def _detect_ec_alert(sensor: Any, history: list[Any], sensor_id: str) -> list[dict[str, Any]]:
    current_value = safe_number(getattr(sensor, "electrical_conductivity", None), 0)
    meta = METRIC_META["ec"]
    threshold = meta["threshold"]
    if current_value <= threshold:
        return []

    timestamp = _timestamp_for(sensor, _now())
    duration_hours, started_at = _collect_active_history_duration(
        current_value=current_value,
        history=history,
        field_name="electrical_conductivity",
        threshold=threshold,
        direction=meta["direction"],
        fallback_timestamp=timestamp,
    )
    distance_ratio = min((current_value - threshold) / meta["danger_span"], 1.0)
    severity = _build_severity(distance_ratio, duration_hours)
    return [
        _make_alert(
            metric_type="ec",
            current_value=current_value,
            threshold_value=threshold,
            severity=severity,
            duration_hours=duration_hours,
            timestamp=started_at,
            sensor_id=sensor_id,
            direction=meta["direction"],
        )
    ]


def _detect_frost_alert(forecasts: list[Any], sensor_id: str) -> list[dict[str, Any]]:
    violating = [forecast for forecast in forecasts[:3] if safe_number(getattr(forecast, "temperature_min", None), 10) < 0]
    if not violating:
        return []

    first = violating[0]
    coldest = min(safe_number(getattr(item, "temperature_min", None), 0) for item in violating)
    duration_hours = max(len(violating) * 24.0, 24.0)
    meta = METRIC_META["temperature"]
    distance_ratio = min((meta["threshold"] - coldest) / meta["danger_span"], 1.0)
    severity = _build_severity(distance_ratio, duration_hours)
    timestamp = _timestamp_for(first, _now())
    return [
        _make_alert(
            metric_type="temperature",
            current_value=coldest,
            threshold_value=meta["threshold"],
            severity=severity,
            duration_hours=duration_hours,
            timestamp=timestamp,
            sensor_id=sensor_id,
            direction=meta["direction"],
            metadata={"forecast_days_impacted": len(violating)},
        )
    ]


def _detect_fungal_risk(sensor: Any, forecasts: list[Any], history: list[Any], sensor_id: str) -> list[dict[str, Any]]:
    humidity_values = [safe_number(getattr(forecast, "humidity_mean", None), None) for forecast in forecasts[:3]]
    humidity_values = [value for value in humidity_values if value is not None]
    if not humidity_values:
        return []

    humidity = sum(humidity_values) / len(humidity_values)
    moisture = safe_number(getattr(sensor, "soil_moisture", None), 0)
    meta = METRIC_META["fungal_risk"]
    threshold = meta["threshold"]
    if humidity <= threshold or moisture <= 60:
        return []

    timestamp = _timestamp_for(sensor, _now())
    duration_hours, started_at = _collect_active_history_duration(
        current_value=moisture,
        history=history,
        field_name="soil_moisture",
        threshold=60.0,
        direction="above",
        fallback_timestamp=timestamp,
    )
    duration_hours = max(duration_hours, len(forecasts[:3]) * 12.0)
    humidity_ratio = min((humidity - threshold) / meta["danger_span"], 1.0)
    moisture_ratio = min((moisture - 60.0) / 20.0, 1.0)
    severity = _build_severity((humidity_ratio * 0.6) + (moisture_ratio * 0.4), duration_hours)
    return [
        _make_alert(
            metric_type="fungal_risk",
            current_value=humidity,
            threshold_value=threshold,
            severity=severity,
            duration_hours=duration_hours,
            timestamp=started_at,
            sensor_id=sensor_id,
            direction=meta["direction"],
            metadata={"soil_moisture": round(moisture, 2)},
        )
    ]


def _sort_alerts(alerts: list[dict[str, Any]]) -> list[dict[str, Any]]:
    return sorted(
        alerts,
        key=lambda alert: (
            SEVERITY_ORDER[alert["severity"]],
            alert["duration_hours"],
            abs(float(alert["current_value"])) if isinstance(alert["current_value"], (int, float)) else 0,
        ),
        reverse=True,
    )


def _build_clusters(alerts: list[dict[str, Any]]) -> list[dict[str, Any]]:
    grouped: dict[str, list[dict[str, Any]]] = defaultdict(list)
    for alert in alerts:
        grouped[alert["domain"]].append(alert)

    clusters: list[dict[str, Any]] = []
    for domain, items in grouped.items():
        ordered = _sort_alerts(items)
        top = ordered[0]
        clusters.append(
            {
                "domain": domain,
                "title": CLUSTER_TITLES.get(domain, domain),
                "alert_count": len(items),
                "highest_severity": top["severity"],
                "primary_metric": top["metric_type"],
                "summary": top["summary"],
                "alert_ids": [f"{item['metric_type']}:{item['timestamp']}" for item in ordered],
            }
        )
    return sorted(clusters, key=lambda cluster: SEVERITY_ORDER[cluster["highest_severity"]], reverse=True)


def _build_alert_stats(alerts: list[dict[str, Any]]) -> list[dict[str, Any]]:
    stats: list[dict[str, Any]] = []
    for metric_type, meta in METRIC_META.items():
        matches = [alert for alert in alerts if alert["metric_type"] == metric_type]
        if not matches:
            continue
        top = _sort_alerts(matches)[0]
        ui = SEVERITY_UI[top["severity"]]
        stats.append(
            {
                "title": meta["title"],
                "count": str(len(matches)),
                "avatarColor": ui["avatarColor"],
                "avatarIcon": meta["icon"],
                "severity": top["severity"],
                "topSummary": top["summary"],
            }
        )
    return stats


def _snapshot_metric(ai_snapshot: dict[str, Any] | None, metric_name: str) -> float | None:
    if not isinstance(ai_snapshot, dict):
        return None
    farm_metrics = ai_snapshot.get("farm_metrics") or {}
    resolved_metrics = farm_metrics.get("resolved_metrics") if isinstance(farm_metrics, dict) else {}
    if not isinstance(resolved_metrics, dict):
        return None
    return safe_number(resolved_metrics.get(metric_name), None)



def _snapshot_weather(ai_snapshot: dict[str, Any] | None) -> dict[str, Any]:
    if not isinstance(ai_snapshot, dict):
        return {}
    weather = ai_snapshot.get("weather") or {}
    forecast = weather.get("forecast") if isinstance(weather, dict) else None
    return forecast if isinstance(forecast, dict) else {}



def _block_metric_alerts(ai_snapshot: dict[str, Any] | None, sensor_id: str) -> list[dict[str, Any]]:
    alerts: list[dict[str, Any]] = []
    if not isinstance(ai_snapshot, dict):
        return alerts
    for block in ai_snapshot.get("block_metrics") or []:
        if not isinstance(block, dict):
            continue
        block_code = str(block.get("block_code") or "default-block")
        metrics = block.get("resolved_metrics") or {}
        moisture = safe_number(metrics.get("soil_moisture"), None)
        if moisture is not None and moisture < 25:
            alerts.append(
                _make_alert(
                    metric_type="moisture",
                    current_value=moisture,
                    threshold_value=25.0,
                    severity="warning" if moisture >= 18 else "danger",
                    duration_hours=1.0,
                    timestamp=_now(),
                    sensor_id=sensor_id,
                    zone_id=block_code,
                    direction="below",
                    metadata={
                        "evaluation_level": "block",
                        "affected_blocks": [block_code],
                        "source": "block_metrics",
                    },
                )
            )
    return alerts



def _sub_block_support(ai_snapshot: dict[str, Any] | None, metric_type: str) -> list[dict[str, Any]]:
    evidence: list[dict[str, Any]] = []
    if not isinstance(ai_snapshot, dict):
        return evidence
    metric_key = {
        "moisture": "soil_moisture",
        "ph": "soil_ph",
        "ec": "electrical_conductivity",
    }.get(metric_type)
    if not metric_key:
        return evidence
    for sub_block in ai_snapshot.get("sub_block_metrics") or []:
        if not isinstance(sub_block, dict):
            continue
        metrics = sub_block.get("resolved_metrics") or {}
        value = metrics.get(metric_key)
        if value is None:
            continue
        evidence.append(
            {
                "block_code": sub_block.get("block_code") or "default-block",
                "sub_block_code": sub_block.get("sub_block_code") or "default-sub-block",
                "metric": metric_key,
                "value": round(float(value), 2),
            }
        )
    return evidence



def build_farm_alerts_tracker(sensor_id: str, context: dict | None = None, ai_bundle: dict | None = None) -> dict:
    context = context or {}
    ai_snapshot = (ai_bundle or {}).get("ai_snapshot") if isinstance(ai_bundle, dict) else None
    forecasts = context.get("forecasts", [])

    if not isinstance(ai_snapshot, dict):
        return {
            "totalAlerts": 0,
            "alerts": [],
            "alertStats": [],
            "alertClusters": [],
            "mostCriticalIssue": None,
            "prioritizedAlertSummaries": [],
            "recommendedOperationalActions": [],
            "humanReadableExplanations": [],
            "source_metadata": {"status": "missing", "fallback": "no_ai_snapshot"},
        }

    alerts = []

    moisture = _snapshot_metric(ai_snapshot, "soil_moisture")
    if moisture is not None and moisture < 25:
        alerts.append(
            _make_alert(
                metric_type="moisture",
                current_value=moisture,
                threshold_value=25.0,
                severity="warning" if moisture >= 18 else "danger",
                duration_hours=1.0,
                timestamp=_now(),
                sensor_id=sensor_id,
                direction="below",
                metadata={
                    "evaluation_level": "farm",
                    "affected_blocks": [item.get("block_code") for item in (ai_snapshot.get("block_metrics") or [])],
                    "supporting_sub_blocks": _sub_block_support(ai_snapshot, "moisture"),
                    "source": "farm_metrics",
                },
            )
        )

    soil_ph = _snapshot_metric(ai_snapshot, "soil_ph")
    if soil_ph is not None and not (6.0 <= soil_ph <= 7.8):
        alerts.append(
            _make_alert(
                metric_type="ph",
                current_value=soil_ph,
                threshold_value="6.0-7.8",
                severity="warning" if 5.5 <= soil_ph <= 8.2 else "danger",
                duration_hours=1.0,
                timestamp=_now(),
                sensor_id=sensor_id,
                direction="below" if soil_ph < 6.0 else "above",
                metadata={
                    "evaluation_level": "farm",
                    "supporting_sub_blocks": _sub_block_support(ai_snapshot, "ph"),
                    "source": "farm_metrics",
                },
            )
        )

    ec = _snapshot_metric(ai_snapshot, "electrical_conductivity")
    if ec is not None and ec > 2.5:
        alerts.append(
            _make_alert(
                metric_type="ec",
                current_value=ec,
                threshold_value=2.5,
                severity="warning" if ec <= 3.2 else "danger",
                duration_hours=1.0,
                timestamp=_now(),
                sensor_id=sensor_id,
                direction="above",
                metadata={
                    "evaluation_level": "farm",
                    "supporting_sub_blocks": _sub_block_support(ai_snapshot, "ec"),
                    "source": "farm_metrics",
                },
            )
        )

    weather = _snapshot_weather(ai_snapshot)
    if weather:
        temp_min = safe_number(weather.get("temperature_min"), None)
        if temp_min is not None and temp_min < 0:
            alerts.append(
                _make_alert(
                    metric_type="temperature",
                    current_value=temp_min,
                    threshold_value=0.0,
                    severity="warning" if temp_min >= -2 else "danger",
                    duration_hours=24.0,
                    timestamp=_now(),
                    sensor_id=sensor_id,
                    direction="below",
                    metadata={
                        "evaluation_level": "farm",
                        "source": "weather_forecast",
                        "weather_policy": "center_location_latest_forecast",
                    },
                )
            )

        humidity = safe_number(weather.get("humidity_mean"), None)
        if humidity is not None and moisture is not None and humidity > 75 and moisture > 60:
            alerts.append(
                _make_alert(
                    metric_type="fungal_risk",
                    current_value=humidity,
                    threshold_value=75.0,
                    severity="warning" if humidity <= 85 else "danger",
                    duration_hours=24.0,
                    timestamp=_now(),
                    sensor_id=sensor_id,
                    direction="above",
                    metadata={
                        "evaluation_level": "farm",
                        "supporting_sub_blocks": _sub_block_support(ai_snapshot, "moisture"),
                        "source": "farm_metrics+weather_forecast",
                        "soil_moisture": round(moisture, 2),
                    },
                )
            )

    alerts.extend(_block_metric_alerts(ai_snapshot, sensor_id))

    ordered_alerts = _sort_alerts(alerts)
    clusters = _build_clusters(ordered_alerts)
    top_alert = ordered_alerts[0] if ordered_alerts else None

    return {
        "totalAlerts": len(ordered_alerts),
        "alerts": ordered_alerts,
        "alertStats": _build_alert_stats(ordered_alerts),
        "alertClusters": clusters,
        "mostCriticalIssue": top_alert,
        "prioritizedAlertSummaries": [alert["summary"] for alert in ordered_alerts],
        "recommendedOperationalActions": [alert["recommended_action"] for alert in ordered_alerts],
        "humanReadableExplanations": [alert["explanation"] for alert in ordered_alerts],
        "source_metadata": {
            "farm_metrics": (ai_snapshot.get("source_metadata") or {}).get("farm_metrics", {}),
            "weather": ((ai_snapshot.get("weather") or {}).get("source_metadata") or {}),
            "default_block_policy": (ai_snapshot.get("aggregation_policy") or {}).get("default_block_policy"),
        },
    }
