"""
Aggregation layer.

Runs all 6 models concurrently using a thread pool (model inference via
joblib/xgboost/statsmodels is CPU-bound and releases the GIL during the
heavy numeric work, so threads — not full async — give real parallelism
here without the complexity of multiprocessing).

This module is called from the Celery task (app/workers/tasks.py), NOT
directly from an API request handler — see the architecture decision
in docs/architecture.md. That keeps slow ML work off the web-serving
threads entirely.
"""

import time
from concurrent.futures import ThreadPoolExecutor
from typing import Any

from app.core.logging_config import get_logger
from app.models_loader.registry import ModelRegistry
from app.services import model_runners
from app.services.feature_builder import FeatureBuilder

logger = get_logger(__name__)
import json as _json

def _load_crops():
    import os
    path = os.path.normpath(os.path.join(os.path.dirname(__file__), '..', '..', 'data', 'lookup', 'crops.json'))
    try:
        with open(path) as f:
            return _json.load(f)
    except:
        return {}

_CROPS = _load_crops()

def _monthly_water_req(crop):
    props = _CROPS.get(crop.lower())
    return (props['water_req_mm'] / 12.0) if props else None


_executor = ThreadPoolExecutor(max_workers=6, thread_name_prefix="model-infer")


def _detect_conflicts(
    climate: dict, feasibility: dict, yield_pred: dict, trend: dict
) -> list[str]:
    conflicts: list[str] = []

    if feasibility.get("label") == "suitable" and climate.get("anomaly_label") == "high":
        conflicts.append(
            "Climate anomaly is high but feasibility model says suitable — "
            "treat the suitable label with caution this month."
        )

    if (
        feasibility.get("label") == "suitable"
        and yield_pred.get("expected_yield_ton_ha", 0) < 1.0
        and not yield_pred.get("degraded")
    ):
        conflicts.append(
            "Yield prediction is unusually low despite a suitable feasibility label."
        )

    if trend.get("trend") == "downward" and feasibility.get("label") == "suitable":
        conflicts.append(
            "Multi-year yield trend is downward even though this month looks suitable — "
            "consider the longer-term pattern, not just this month."
        )

    return conflicts


def run_full_analysis(
    registry: ModelRegistry,
    feature_builder: FeatureBuilder,
    district: str,
    crop: str,
    month: int,
) -> dict[str, Any]:
    """
    Synchronous, blocking function — this is exactly what we want inside
    a Celery worker task. Building it as a plain function (not async)
    keeps it trivially testable and avoids mixing asyncio with the
    thread-pool model calls.
    """
    start = time.perf_counter()

    feature_vector = feature_builder.build(district, crop, month)

    futures = {
        "climate": _executor.submit(model_runners.run_climate_model, registry, district, month),        
        "feasibility": _executor.submit(model_runners.run_feasibility_model, registry, feature_vector, crop, district),
        "yield_prediction": _executor.submit(model_runners.run_yield_model, registry, feature_vector),
        "trend": _executor.submit(model_runners.run_trend_model, registry, feature_vector),
        "recommendation": _executor.submit(
            model_runners.run_recommendation_model,
            registry, district, month, feature_builder.build, crop,
        ),
        "market": _executor.submit(model_runners.run_market_model, registry, district, crop),
    }

    results = {key: future.result() for key, future in futures.items()}

    conflicts = _detect_conflicts(
        results["climate"], results["feasibility"],
        results["yield_prediction"], results["trend"],
    )

    degraded_models = [k for k, v in results.items() if isinstance(v, dict) and v.get("degraded")]
    if degraded_models:
        logger.warning(f"Analysis completed with degraded models: {degraded_models}")

    elapsed_ms = int((time.perf_counter() - start) * 1000)

    return {
        "climate": results["climate"],
        "feasibility": results["feasibility"],
        "yield_prediction": results["yield_prediction"],
        "trend": results["trend"],
        "recommendation": results["recommendation"],
        "market": results["market"],
        "conflicts": conflicts,
        "degraded_models": degraded_models,
        "processing_ms": elapsed_ms,
    }
