from copy import deepcopy

from django.db import transaction
from drf_spectacular.utils import (
    OpenApiExample,
    OpenApiResponse,
    extend_schema,
    inline_serializer,
)
from rest_framework import serializers as drf_serializers
from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import APIView

from config.integration_contract import build_integration_meta
from config.openapi import build_envelope_serializer, build_response
from .models import ParameterUpdateLog, SensorData, SensorParameter
from .serializers import (
    FarmDetailSerializer,
    SensorDataResponseSerializer,
    SensorDataUpdateSerializer,
    SensorParameterSerializer,
)
from .services import (
    BackendSyncError,
    assign_farm_plants_from_backend_ids,
    ExternalDataSyncError,
    ensure_location_and_weather_data,
    get_farm_details,
    resolve_center_location_from_boundary,
    sync_devices_from_sensor_data,
    sync_sensor_parameters_from_payload,
    sync_plant_catalog_from_backend,
)


SensorDataEnvelopeSerializer = build_envelope_serializer(
    "SensorDataEnvelopeSerializer",
    SensorDataResponseSerializer,
)
SensorDataValidationErrorSerializer = build_envelope_serializer(
    "SensorDataValidationErrorSerializer",
    data_required=False,
    allow_null=True,
)
SensorDataNotFoundSerializer = build_envelope_serializer(
    "SensorDataNotFoundSerializer",
    data_required=False,
    allow_null=True,
)
FarmDetailEnvelopeSerializer = build_envelope_serializer(
    "FarmDetailEnvelopeSerializer",
    FarmDetailSerializer,
)
SensorParameterResponseSerializer = build_envelope_serializer(
    "SensorParameterEnvelopeSerializer",
    inline_serializer(
        name="SensorParameterPayloadSerializer",
        fields={
            "id": drf_serializers.IntegerField(),
            "sensor_key": drf_serializers.CharField(),
            "code": drf_serializers.CharField(),
            "name_fa": drf_serializers.CharField(),
            "unit": drf_serializers.CharField(),
            "data_type": drf_serializers.CharField(),
            "metadata": drf_serializers.JSONField(),
            "created_at": drf_serializers.DateTimeField(),
            "action": drf_serializers.CharField(),
        },
    ),
)


class FarmDataUpsertView(APIView):
    """
    ایجاد یا آپدیت داده farm.
    """

    @extend_schema(
        tags=["Farm Data"],
        summary="ایجاد یا آپدیت داده farm",
        description=(
            "داده farm را با `POST /api/farm-data/` ایجاد یا آپدیت می‌کند. "
            "`farm_uuid` باید از API ارسال شود و هرگز خودکار ساخته نمی‌شود. "
            "مرز مزرعه را می‌گیرد، مرکز زمین را خودش محاسبه و در location_data ذخیره می‌کند. "
            "رکورد آب‌وهوا هم از همان مرکز زمین به‌صورت خودکار پیدا می‌شود. "
            "در این مرحله برای location_data هیچ ریکوئست خارجی برای بلوک‌ها زده نمی‌شود. "
            'خوانش‌ها داخل `sensor_payload` مثل `{"sensor-7-1": {...}}` نگه‌داری می‌شوند.'
        ),
        request=SensorDataUpdateSerializer,
        responses={
            200: build_response(
                SensorDataEnvelopeSerializer,
                "داده farm با موفقیت به‌روزرسانی شد.",
            ),
            201: build_response(
                SensorDataEnvelopeSerializer,
                "داده farm با موفقیت ایجاد شد.",
            ),
            400: build_response(
                SensorDataValidationErrorSerializer,
                "داده ورودی نامعتبر است.",
            ),
            502: build_response(
                SensorDataNotFoundSerializer,
                "واکشی داده خاک یا آب‌وهوا از سرویس بیرونی ناموفق بود.",
            ),
        },
        examples=[
            OpenApiExample(
                "نمونه درخواست",
                value={
                    "farm_uuid": "550e8400-e29b-41d4-a716-446655440000",
                    "farm_boundary": {
                        "type": "Polygon",
                        "coordinates": [
                            [
                                [51.3900, 35.7000],
                                [51.4100, 35.7000],
                                [51.4100, 35.7200],
                                [51.3900, 35.7200],
                                [51.3900, 35.7000],
                            ]
                        ],
                    },
                    "block_count": 3,
                    "sensor_payload": {
                        "sensor-7-1": {
                            "soil_moisture": 45.2,
                            "soil_temperature": 22.5,
                            "soil_ph": 6.8,
                            "electrical_conductivity": 1.2,
                            "nitrogen": 30.0,
                            "phosphorus": 15.0,
                            "potassium": 20.0,
                        }
                    },
                },
                request_only=True,
            ),
            OpenApiExample(
                "نمونه چند سنسور",
                value={
                    "farm_uuid": "550e8400-e29b-41d4-a716-446655440000",
                    "farm_boundary": {
                        "corners": [
                            {"lat": 35.7000, "lon": 51.3900},
                            {"lat": 35.7000, "lon": 51.4100},
                            {"lat": 35.7200, "lon": 51.4100},
                            {"lat": 35.7200, "lon": 51.3900},
                        ]
                    },
                    "block_count": 2,
                    "sensor_payload": {
                        "sensor-7-1": {
                            "soil_moisture": 45.2,
                            "soil_temperature": 22.5,
                        },
                        "leaf-sensor": {
                            "leaf_wetness": 11.0,
                            "leaf_temperature": 19.3,
                        },
                    },
                },
                request_only=True,
            ),
        ],
    )
    def post(self, request):
        serializer = SensorDataUpdateSerializer(data=request.data)
        if not serializer.is_valid():
            return Response(
                {"code": 400, "msg": "داده نامعتبر.", "data": serializer.errors},
                status=status.HTTP_400_BAD_REQUEST,
            )

        farm_uuid = serializer.validated_data["farm_uuid"]
        farm_boundary = serializer.validated_data["farm_boundary"]
        block_count = serializer.validated_data.get("block_count", 1)
        plant_ids = serializer.validated_data.get("plant_ids")
        irrigation_method_id = serializer.validated_data.get("irrigation_method_id")
        sensor_payload = serializer.validated_data.get("sensor_payload", {})
        try:
            center_location = resolve_center_location_from_boundary(
                farm_boundary,
                block_count=block_count,
            )
        except ValueError as exc:
            return Response(
                {"code": 400, "msg": "داده نامعتبر.", "data": {"farm_boundary": [str(exc)]}},
                status=status.HTTP_400_BAD_REQUEST,
            )
        try:
            center_location, weather_forecast = ensure_location_and_weather_data(
                center_location
            )
        except ExternalDataSyncError as exc:
            return Response(
                {"code": 502, "msg": str(exc), "data": None},
                status=status.HTTP_502_BAD_GATEWAY,
            )

        with transaction.atomic():
            sync_sensor_parameters_from_payload(sensor_payload)
            farm_data, created = SensorData.objects.get_or_create(
                farm_uuid=farm_uuid,
                defaults={
                    "center_location": center_location,
                    "weather_forecast": weather_forecast,
                    "sensor_payload": sensor_payload,
                },
            )

            if not created and sensor_payload:
                merged_payload = deepcopy(farm_data.sensor_payload or {})
                for sensor_key, sensor_values in sensor_payload.items():
                    current_values = merged_payload.get(sensor_key, {})
                    if not isinstance(current_values, dict):
                        current_values = {}
                    current_values.update(sensor_values)
                    merged_payload[sensor_key] = current_values
                farm_data.sensor_payload = merged_payload
            elif created:
                farm_data.sensor_payload = sensor_payload

            farm_data.center_location = center_location
            farm_data.weather_forecast = weather_forecast
            if "irrigation_method_id" in serializer.validated_data:
                farm_data.irrigation_method_id = irrigation_method_id
            if not created:
                farm_data.save(
                    update_fields=[
                        "center_location",
                        "weather_forecast",
                        "sensor_payload",
                        "irrigation_method",
                        "updated_at",
                    ]
                )
            else:
                farm_data.save()

            sync_devices_from_sensor_data(farm_data)

            if plant_ids is not None:
                try:
                    assign_farm_plants_from_backend_ids(farm_data, plant_ids)
                except BackendSyncError as exc:
                    return Response(
                        {"code": 400, "msg": str(exc), "data": None},
                        status=status.HTTP_400_BAD_REQUEST,
                    )

        response_status = (
            status.HTTP_201_CREATED if created else status.HTTP_200_OK
        )
        return Response(
            {
                "code": 201 if created else 200,
                "msg": "success",
                "data": SensorDataResponseSerializer(farm_data).data,
                "meta": build_integration_meta(
                    flow_type="ai_owned_derived_output",
                    source_type="provider",
                    source_service="ai_farm_data",
                    ownership="ai",
                    live=True,
                    cached=False,
                    generated_at=farm_data.updated_at,
                    notes=["AI farm_data stores a derived read-model enriched with location and weather data."],
                ),
            },
            status=response_status,
        )


class FarmDetailView(APIView):
    @extend_schema(
        tags=["Farm Data"],
        summary="دریافت همه اطلاعات farm",
        description=(
            "اطلاعات تجمیعی farm را برمی‌گرداند. "
            "برای resolved_metrics، داده‌های sensor روی داده‌های خاک اولویت دارند "
            "و در حالت چند سنسوره، مقادیر متعارض به‌صورت deterministic تجمیع می‌شوند."
        ),
        examples=[
            OpenApiExample(
                "نمونه مسیر farm detail",
                value={"farm_uuid": "11111111-1111-1111-1111-111111111111"},
                parameter_only=("farm_uuid", "path"),
            ),
        ],
        responses={
            200: build_response(
                FarmDetailEnvelopeSerializer,
                "اطلاعات farm با موفقیت بازگردانده شد.",
            ),
            404: build_response(
                SensorDataNotFoundSerializer,
                "farm موردنظر یافت نشد.",
            ),
        },
    )
    def get(self, request, farm_uuid):
        data = get_farm_details(str(farm_uuid))
        if data is None:
            return Response(
                {"code": 404, "msg": "farm یافت نشد.", "data": None},
                status=status.HTTP_404_NOT_FOUND,
            )

        return Response(
            {
                "code": 200,
                "msg": "success",
                "data": data,
                "meta": build_integration_meta(
                    flow_type="ai_owned_derived_output",
                    source_type="db",
                    source_service="ai_farm_data",
                    ownership="ai",
                    live=False,
                    cached=True,
                    snapshot_at=getattr(data, "get", lambda *_: None)("updated_at") if isinstance(data, dict) else None,
                ),
            },
            status=status.HTTP_200_OK,
        )


class PlantCatalogSyncView(APIView):
    @extend_schema(
        tags=["Farm Data"],
        summary="همگام‌سازی کاتالوگ گیاه از Backend",
        description="payload گیاه‌های canonical را از Backend دریافت و در `farm_data` snapshot می‌کند.",
        request=drf_serializers.ListSerializer(
            child=inline_serializer(
                name="PlantCatalogSyncItem",
                fields={
                    "id": drf_serializers.IntegerField(),
                    "name": drf_serializers.CharField(),
                },
            )
        ),
        responses={
            200: OpenApiResponse(description="کاتالوگ گیاه با موفقیت sync شد."),
            400: OpenApiResponse(description="payload نامعتبر است."),
        },
    )
    def post(self, request):
        if not isinstance(request.data, list):
            return Response(
                {"code": 400, "msg": "payload باید آرایه‌ای از گیاه‌ها باشد.", "data": None},
                status=status.HTTP_400_BAD_REQUEST,
            )
        try:
            snapshots = sync_plant_catalog_from_backend(request.data)
        except BackendSyncError as exc:
            return Response(
                {"code": 400, "msg": str(exc), "data": None},
                status=status.HTTP_400_BAD_REQUEST,
            )
        return Response(
            {
                "code": 200,
                "msg": "success",
                "data": {
                    "count": len(snapshots),
                    "plant_ids": [snapshot.backend_plant_id for snapshot in snapshots],
                },
                "meta": build_integration_meta(
                    flow_type="backend_owned_data_with_ai_enrichment",
                    source_type="db",
                    source_service="ai_farm_data_plant_catalog",
                    ownership="backend",
                    live=False,
                    cached=False,
                    generated_at=snapshots[-1].updated_at if snapshots else None,
                    notes=["Backend is canonical for plant catalog; AI stores snapshots for derived services."],
                ),
            },
            status=status.HTTP_200_OK,
        )


class SensorParameterCreateView(APIView):
    """
    اضافه کردن پارامتر جدید و ثبت در ParameterUpdateLog.
    """

    @extend_schema(
        tags=["Farm Parameters"],
        summary="افزودن/ویرایش پارامتر سنسور",
        description="پارامتر جدید اضافه یا پارامتر موجود را ویرایش می‌کند و در لاگ ثبت می‌شود.",
        request=SensorParameterSerializer,
        responses={
            201: build_response(
                SensorParameterResponseSerializer,
                "پارامتر سنسور با موفقیت ایجاد یا ویرایش شد.",
            ),
            400: build_response(
                SensorDataValidationErrorSerializer,
                "داده ورودی نامعتبر است.",
            ),
        },
        examples=[
            OpenApiExample(
                "نمونه درخواست",
                value={
                    "sensor_key": "sensor-7-1",
                    "code": "soil_moisture",
                    "name_fa": "رطوبت خاک",
                    "unit": "%",
                    "data_type": "float",
                    "metadata": {"min": 0, "max": 100},
                },
                request_only=True,
            ),
        ],
    )
    def post(self, request):
        serializer = SensorParameterSerializer(data=request.data)
        if not serializer.is_valid():
            return Response(
                {"code": 400, "msg": "داده نامعتبر.", "data": serializer.errors},
                status=status.HTTP_400_BAD_REQUEST,
            )

        sensor_key = serializer.validated_data.get("sensor_key")
        code = serializer.validated_data["code"]
        name_fa = serializer.validated_data["name_fa"]
        unit = serializer.validated_data.get("unit", "")
        data_type = serializer.validated_data.get("data_type", "")
        metadata = serializer.validated_data.get("metadata", {})

        with transaction.atomic():
            parameter, created = SensorParameter.objects.update_or_create(
                sensor_key=sensor_key,
                code=code,
                defaults={
                    "name_fa": name_fa,
                    "unit": unit,
                    "data_type": data_type,
                    "metadata": metadata,
                },
            )
            action = (
                ParameterUpdateLog.ACTION_ADDED
                if created
                else ParameterUpdateLog.ACTION_MODIFIED
            )
            ParameterUpdateLog.objects.create(
                parameter=parameter,
                action=action,
                payload={
                    "sensor_key": parameter.sensor_key,
                    "code": parameter.code,
                    "name_fa": parameter.name_fa,
                    "unit": parameter.unit,
                    "data_type": parameter.data_type,
                    "metadata": parameter.metadata,
                },
            )

        return Response(
            {
                "code": 201,
                "msg": "success",
                "data": {
                    "id": parameter.id,
                    "sensor_key": parameter.sensor_key,
                    "code": parameter.code,
                    "name_fa": parameter.name_fa,
                    "unit": parameter.unit,
                    "data_type": parameter.data_type,
                    "metadata": parameter.metadata,
                    "created_at": parameter.created_at,
                    "action": action,
                },
                "meta": build_integration_meta(
                    flow_type="ai_owned_derived_output",
                    source_type="db",
                    source_service="ai_farm_parameters",
                    ownership="ai",
                    live=False,
                    cached=False,
                    generated_at=parameter.created_at,
                ),
            },
            status=status.HTTP_201_CREATED,
        )
