"""
Celery task definitions.

run_crop_analysis is the single task type this system queues. It is
intentionally synchronous (no async/await inside) because Celery's
worker model is process+thread based, not asyncio based — mixing the
two reliably is more complexity than this system needs.

Failure handling: if any unexpected exception escapes everything inside
(which should be rare, since aggregator.py and model_runners.py already
catch and degrade per-model), the task is marked FAILURE in the Celery
result backend, and the failure is also written to failed_request_logs
so the project coordinator can see it in the database, not just in logs.
"""

import time

from celery.utils.log import get_task_logger

from app.core.config import get_settings
from app.db.models import AnalysisRecord, FailedRequestLog
from app.db.sync_session import sync_db_session
from app.models_loader.registry import model_registry
from app.services.aggregator import run_full_analysis
from app.services.feature_builder import feature_builder
from app.services.llm_service import llm_service
from app.services.notification_service import notify_feasibility_change_if_needed
from app.workers.celery_app import celery_app

settings = get_settings()
logger = get_task_logger(__name__)


@celery_app.task(name="run_crop_analysis", bind=True, max_retries=1)
def run_crop_analysis(
    self,
    request_id: str,
    district: str,
    crop: str,
    month: int,
    api_key_prefix: str | None = None,
    client_ip: str | None = None,
    user_id: str | None = None,
    crop_plan_id: str | None = None,
) -> dict:
    start = time.perf_counter()

    try:
        model_outputs = run_full_analysis(model_registry, feature_builder, district, crop, month)

        explanation = llm_service.get_explanation(district, crop, month, model_outputs)

        total_ms = int((time.perf_counter() - start) * 1000)

        result = {
            "request_id": request_id,
            "input": {"crop": crop, "location": district, "month": month},
            "models": {
                "climate": model_outputs["climate"],
                "feasibility": model_outputs["feasibility"],
                "yield_prediction": model_outputs["yield_prediction"],
                "trend": model_outputs["trend"],
                "recommendation": model_outputs["recommendation"],
                "market": model_outputs["market"],
                "conflicts": model_outputs["conflicts"],
            },
            "explanation": explanation,
            "model_version": settings.MODEL_VERSION,
            "processing_ms": total_ms,
            "cached": False,
        }

        _persist_success(
            request_id, district, crop, month, model_outputs, result, total_ms,
            api_key_prefix, client_ip, user_id, crop_plan_id,
        )

        if crop_plan_id:
            notify_feasibility_change_if_needed(
                crop_plan_id, model_outputs["feasibility"].get("label")
            )

        return result

    except Exception as exc:
        logger.error(f"run_crop_analysis failed for request_id={request_id}: {exc}", exc_info=True)
        _persist_failure(request_id, district, crop, month, str(exc))
        raise


def _persist_success(
    request_id: str,
    district: str,
    crop: str,
    month: int,
    model_outputs: dict,
    full_result: dict,
    processing_ms: int,
    api_key_prefix: str | None,
    client_ip: str | None,
    user_id: str | None = None,
    crop_plan_id: str | None = None,
) -> None:
    try:
        with sync_db_session() as session:
            record = AnalysisRecord(
                request_id=request_id,
                user_id=user_id,
                crop_plan_id=crop_plan_id,
                crop=crop,
                district=district,
                month=month,
                feasibility_label=model_outputs["feasibility"].get("label"),
                expected_yield_ton_ha=model_outputs["yield_prediction"].get("expected_yield_ton_ha"),
                had_conflicts=bool(model_outputs["conflicts"]),
                degraded_models=",".join(model_outputs.get("degraded_models", [])),
                full_result=full_result,
                model_version=settings.MODEL_VERSION,
                served_from_cache=False,
                processing_ms=processing_ms,
                api_key_prefix=api_key_prefix,
                client_ip=client_ip,
            )
            session.add(record)
    except Exception as exc:
        # DB write failure must never mask a successful analysis from the
        # client — log loudly and move on, the result is still returned.
        logger.error(f"Failed to persist analysis record for request_id={request_id}: {exc}", exc_info=True)


def _persist_failure(request_id: str, district: str, crop: str, month: int, error_message: str) -> None:
    try:
        with sync_db_session() as session:
            record = FailedRequestLog(
                request_id=request_id,
                error_code="TASK_EXECUTION_FAILED",
                error_message=error_message,
                input_payload={"crop": crop, "location": district, "month": month},
            )
            session.add(record)
    except Exception as exc:
        logger.error(f"Failed to persist failure log for request_id={request_id}: {exc}", exc_info=True)
