from __future__ import annotations

from contextlib import ExitStack
from types import SimpleNamespace
from unittest.mock import patch
import uuid

from django.apps import apps
from django.test import override_settings

from crop_simulation.models import SimulationRun, SimulationScenario
from farm_alerts.models import FarmAlertNotification
from farm_data.models import SensorData
from farm_data.services import assign_farm_plants_from_backend_ids
from integration_tests.base import IntegrationAPITestCase, square_boundary


class FakeAsyncResult:
    def __init__(self, *, state: str, result=None, info=None):
        self.state = state
        self.result = result
        self.info = info


@override_settings(ROOT_URLCONF="config.urls")
class ReportingAndAiJourneyTests(IntegrationAPITestCase):
    def setUp(self) -> None:
        super().setUp()
        self.irrigation_method = self.create_irrigation_method_via_api("Analytics Drip")
        self.primary_plant = self.create_plant_via_api("Tomato")
        self.secondary_plant = self.create_plant_via_api("Pepper")
        self.farm_uuid = uuid.uuid4()
        self.upsert_farm_via_api(
            farm_uuid=self.farm_uuid,
            plant_ids=[self.primary_plant["id"], self.secondary_plant["id"]],
            irrigation_method_id=self.irrigation_method["id"],
            sensor_payload={
                "sensor-7-1": {
                    "soil_moisture": 46.0,
                    "soil_temperature": 24.2,
                    "soil_ph": 6.5,
                    "electrical_conductivity": 1.3,
                    "nitrogen": 20.0,
                    "phosphorus": 11.0,
                    "potassium": 18.0,
                    "timestamp": "2026-04-10T06:30:00Z",
                }
            },
        )
        self.seed_neighbor_farm()

    def seed_neighbor_farm(self) -> None:
        neighbor_location = self.create_complete_location(
            lat=35.706000,
            lon=51.406000,
            boundary=square_boundary(35.706000, 51.406000, delta=0.008),
            clay_values=(19.0, 16.5, 13.0),
            nitrogen_values=(12.0, 10.0, 7.0),
        )
        self.seed_weather_forecasts(
            neighbor_location,
            start=self.forecast_start,
            days=7,
            temperature_base=24.0,
            et0_base=3.8,
        )
        neighbor_sensor = SensorData.objects.create(
            farm_uuid=uuid.uuid4(),
            center_location=neighbor_location,
            weather_forecast=neighbor_location.weather_forecasts.order_by("forecast_date").first(),
            irrigation_method_id=self.irrigation_method["id"],
            sensor_payload={
                "sensor-7-1": {
                    "soil_moisture": 38.5,
                    "soil_temperature": 25.0,
                    "soil_ph": 6.7,
                    "electrical_conductivity": 1.0,
                    "nitrogen": 16.0,
                    "timestamp": "2026-04-10T06:35:00Z",
                }
            },
        )
        assign_farm_plants_from_backend_ids(neighbor_sensor, [self.primary_plant["id"]])

    def test_reporting_endpoints_read_from_persisted_farm_context(self) -> None:
        soil_response = self.client.get(
            "/api/location-data/",
            data={"lat": f"{self.primary_lat:.6f}", "lon": f"{self.primary_lon:.6f}"},
        )
        self.assertEqual(soil_response.status_code, 200)
        self.assertEqual(soil_response.json()["data"]["source"], "database")
        self.assertIn("satellite_snapshots", soil_response.json()["data"])

        weather_response = self.client.post(
            "/api/weather/farm-card/",
            data={"farm_uuid": str(self.farm_uuid)},
            format="json",
        )
        self.assertEqual(weather_response.status_code, 200)
        self.assertEqual(weather_response.json()["data"]["condition"], "صاف")

        with patch(
            "weather.water_need_prediction.get_water_need_prediction_insight",
            return_value={
                "summary": "Water demand is moderate for the next week.",
                "irrigation_outlook": "Increase slowly.",
                "recommended_action": "Keep early morning irrigation.",
                "risk_note": "Watch evapotranspiration after day 4.",
                "confidence": 0.88,
                "knowledge_base": "water_need_prediction",
                "tone_file": "config/tones/water_need_prediction_tone.txt",
                "raw_response": "{\"summary\": \"ok\"}",
            },
        ):
            water_need_response = self.client.post(
                "/api/weather/water-need-prediction/",
                data={"farm_uuid": str(self.farm_uuid)},
                format="json",
            )
        self.assertEqual(water_need_response.status_code, 200)
        self.assertGreater(water_need_response.json()["data"]["totalNext7Days"], 0)
        self.assertEqual(
            water_need_response.json()["data"]["knowledge_base"],
            "water_need_prediction",
        )

        economy_response = self.client.post(
            "/api/economy/overview/",
            data={"farm_uuid": str(self.farm_uuid)},
            format="json",
        )
        self.assertEqual(economy_response.status_code, 200)
        self.assertEqual(economy_response.json()["data"]["farm_uuid"], str(self.farm_uuid))

        heatmap_response = self.client.post(
            "/api/soile/moisture-heatmap/",
            data={"farm_uuid": str(self.farm_uuid)},
            format="json",
        )
        self.assertEqual(heatmap_response.status_code, 200)
        self.assertGreater(len(heatmap_response.json()["data"]["grid_cells"]), 0)
        self.assertGreaterEqual(len(heatmap_response.json()["data"]["sensor_points"]), 1)

        soil_health_response = self.client.post(
            "/api/soile/health-summary/",
            data={"farm_uuid": str(self.farm_uuid)},
            format="json",
        )
        self.assertEqual(soil_health_response.status_code, 200)
        self.assertIn("healthScore", soil_health_response.json()["data"])

        with patch(
            "soile.services.get_soil_anomaly_insight",
            return_value={
                "interpretation": {
                    "summary": "No critical anomaly detected.",
                    "recommended_action": "Continue monitoring.",
                },
                "knowledge_base": "soil_anomaly",
                "raw_response": "{\"status\": \"ok\"}",
            },
        ):
            anomaly_response = self.client.post(
                "/api/soile/anomaly-detection/",
                data={"farm_uuid": str(self.farm_uuid)},
                format="json",
            )
        self.assertEqual(anomaly_response.status_code, 200)
        self.assertEqual(anomaly_response.json()["data"]["knowledge_base"], "soil_anomaly")

        ndvi_response = self.client.post(
            "/api/location-data/ndvi-health/",
            data={"farm_uuid": str(self.farm_uuid)},
            format="json",
        )
        self.assertEqual(ndvi_response.status_code, 200)
        self.assertEqual(ndvi_response.json()["data"]["vegetation_health_class"], "Healthy")

        broken_simulation_service = SimpleNamespace(
            get_water_stress=lambda **_kwargs: (_ for _ in ()).throw(RuntimeError("simulation offline"))
        )
        crop_simulation_app = apps.get_app_config("crop_simulation")
        with patch.object(
            crop_simulation_app,
            "get_water_stress_service",
            return_value=broken_simulation_service,
        ):
            water_stress_response = self.client.post(
                "/api/irrigation/water-stress/",
                data={"farm_uuid": str(self.farm_uuid)},
                format="json",
            )
        self.assertEqual(water_stress_response.status_code, 500)

    def test_ai_assistant_and_recommendation_endpoints_use_farm_context(self) -> None:
        with ExitStack() as stack:
            stack.enter_context(
                patch("rag.config.load_rag_config", return_value=SimpleNamespace())
            )
            stack.enter_context(
                patch(
                    "rag.views.chat_rag_stream",
                    return_value=iter(["Farm looks stable. ", "Moisture is acceptable."]),
                )
            )
            stack.enter_context(
                patch(
                    "rag.services.irrigation.get_irrigation_recommendation",
                    return_value={
                        "sections": [
                            {
                                "type": "recommendation",
                                "title": "برنامه آبیاری بهینه",
                                "content": "هفته ای 3 نوبت آبیاری انجام شود.",
                            }
                        ],
                    },
                )
            )
            stack.enter_context(
                patch(
                    "rag.services.fertilization.get_fertilization_recommendation",
                    return_value={
                        "sections": [
                            {
                                "type": "recommendation",
                                "title": "برنامه کودهی بهینه",
                                "fertilizerType": "15-5-30",
                                "amount": "60 kg",
                            }
                        ],
                    },
                )
            )
            stack.enter_context(
                patch(
                    "pest_disease.views.get_pest_disease_detection",
                    return_value={
                        "diagnosis": "low risk leaf stress",
                        "confidence": 0.81,
                    },
                )
            )
            stack.enter_context(
                patch(
                    "pest_disease.views.get_pest_disease_risk",
                    return_value={
                        "riskLevel": "medium",
                        "topRisk": "powdery mildew",
                    },
                )
            )

            chat_response = self.client.post(
                "/api/rag/chat/",
                data={
                    "farm_uuid": str(self.farm_uuid),
                    "query": "Give me a short farm update",
                    "history": [],
                },
                format="json",
            )
            self.assertEqual(chat_response.status_code, 200)
            streamed_text = b"".join(chat_response.streaming_content).decode("utf-8")
            self.assertIn("Moisture is acceptable", streamed_text)

            irrigation_recommend_response = self.client.post(
                "/api/irrigation/recommend/",
                data={
                    "farm_uuid": str(self.farm_uuid),
                    "plant_name": "Tomato",
                    "growth_stage": "flowering",
                    "irrigation_method_name": "Analytics Drip",
                },
                format="json",
            )
            self.assertEqual(irrigation_recommend_response.status_code, 200)
            self.assertEqual(
                irrigation_recommend_response.json()["data"]["sections"][0]["type"],
                "recommendation",
            )

            fertilization_recommend_response = self.client.post(
                "/api/fertilization/recommend/",
                data={
                    "farm_uuid": str(self.farm_uuid),
                    "plant_name": "Tomato",
                    "growth_stage": "flowering",
                },
                format="json",
            )
            self.assertEqual(fertilization_recommend_response.status_code, 200)
            self.assertEqual(
                fertilization_recommend_response.json()["data"]["sections"][0]["fertilizerType"],
                "15-5-30",
            )

            pest_detect_response = self.client.post(
                "/api/pest-disease/detect/",
                data={
                    "farm_uuid": str(self.farm_uuid),
                    "plant_name": "Tomato",
                    "query": "Check leaf condition",
                    "image_urls": ["https://example.com/leaf.jpg"],
                },
                format="json",
            )
            self.assertEqual(pest_detect_response.status_code, 200)
            self.assertEqual(pest_detect_response.json()["data"]["confidence"], 0.81)

            pest_risk_response = self.client.post(
                "/api/pest-disease/risk/",
                data={
                    "farm_uuid": str(self.farm_uuid),
                    "plant_name": "Tomato",
                    "growth_stage": "flowering",
                },
                format="json",
            )
            self.assertEqual(pest_risk_response.status_code, 200)

    def test_alert_and_crop_simulation_endpoints_persist_records(self) -> None:
        def tracker_stub(*, farm_uuid: str, query: str | None = None):
            from farm_alerts.services import _save_notifications, _serialize_notification

            saved = _save_notifications(
                farm_uuid=str(farm_uuid),
                endpoint=FarmAlertNotification.ENDPOINT_TRACKER,
                notifications=[
                    {
                        "level": FarmAlertNotification.LEVEL_WARNING,
                        "title": "Moisture drift",
                        "message": "Moisture dropped below the target band.",
                        "suggested_action": "Review the next irrigation cycle.",
                        "source_alert_id": "soil-moisture:1",
                        "source_metric_type": "soil_moisture",
                    }
                ],
            )
            return {
                "headline": "Tracker result",
                "overview": "One warning was recorded.",
                "status_level": "warning",
                "query": query,
                "notifications": [_serialize_notification(item) for item in saved],
            }

        def timeline_stub(*, farm_uuid: str, query: str | None = None):
            from farm_alerts.services import _save_notifications, _serialize_notification

            saved = _save_notifications(
                farm_uuid=str(farm_uuid),
                endpoint=FarmAlertNotification.ENDPOINT_TIMELINE,
                notifications=[
                    {
                        "level": FarmAlertNotification.LEVEL_INFO,
                        "title": "Irrigation completed",
                        "message": "The latest drip cycle finished successfully.",
                        "suggested_action": "Recheck moisture after sunrise.",
                        "source_alert_id": "irrigation:1",
                        "source_metric_type": "irrigation",
                    }
                ],
            )
            return {
                "headline": "Timeline result",
                "overview": "One event was stored.",
                "query": query,
                "timeline": [
                    {
                        "timestamp": "2026-04-10T05:30:00Z",
                        "level": "info",
                        "title": "Irrigation completed",
                        "description": "Cycle finished.",
                        "source_alert_id": "irrigation:1",
                        "source_metric_type": "irrigation",
                    }
                ],
                "notifications": [_serialize_notification(item) for item in saved],
            }

        with patch("farm_alerts.views.get_farm_alerts_tracker", side_effect=tracker_stub):
            tracker_response = self.client.post(
                "/api/farm-alerts/tracker/",
                data={"farm_uuid": str(self.farm_uuid), "query": "status"},
                format="json",
            )
        self.assertEqual(tracker_response.status_code, 200)

        with patch("farm_alerts.views.get_farm_alerts_timeline", side_effect=timeline_stub):
            timeline_response = self.client.post(
                "/api/farm-alerts/timeline/",
                data={"farm_uuid": str(self.farm_uuid)},
                format="json",
            )
        self.assertEqual(timeline_response.status_code, 200)
        self.assertEqual(
            FarmAlertNotification.objects.filter(farm_uuid=self.farm_uuid).count(),
            2,
        )

        current_chart_service = SimpleNamespace(
            simulate=lambda **_kwargs: {
                "farm_uuid": str(self.farm_uuid),
                "plant_name": "Tomato",
                "engine": "stub",
                "model_name": "integration-model",
                "scenario_id": None,
                "simulation_warning": "",
                "categories": ["day1", "day2"],
                "series": [{"name": "LAI", "data": [0.8, 1.1]}],
                "summary": {"expectedTrend": "up"},
                "current_state": {"soilMoisture": 46.0},
                "metrics": {"yieldEstimate": 8.2},
                "daily_output": [{"day": "2026-04-10", "lai": 0.8}],
            }
        )
        harvest_service = SimpleNamespace(
            get_harvest_prediction=lambda **_kwargs: {
                "date": "2026-07-15",
                "dateFormatted": "15 Jul 2026",
                "daysUntil": 96,
                "description": "Expected harvest window",
                "optimalWindowStart": "2026-07-10",
                "optimalWindowEnd": "2026-07-20",
                "gddDetails": {"current": 420, "target": 1220},
            }
        )
        yield_service = SimpleNamespace(
            get_yield_prediction=lambda **_kwargs: {
                "farm_uuid": str(self.farm_uuid),
                "plant_name": "Tomato",
                "predictedYieldTons": 8.4,
                "predictedYieldRaw": 8400.0,
                "unit": "t/ha",
                "sourceUnit": "kg/ha",
                "simulationEngine": "stub",
                "simulationModel": "integration-model",
                "scenarioId": None,
                "simulationWarning": "",
                "supportingMetrics": {"biomass": 12.1},
            }
        )
        crop_simulation_app = apps.get_app_config("crop_simulation")
        with (
            patch.object(
                crop_simulation_app,
                "get_current_farm_chart_simulator",
                return_value=current_chart_service,
            ),
            patch.object(
                crop_simulation_app,
                "get_harvest_prediction_service",
                return_value=harvest_service,
            ),
            patch.object(
                crop_simulation_app,
                "get_yield_prediction_service",
                return_value=yield_service,
            ),
        ):
            current_chart_response = self.client.post(
                "/api/crop-simulation/current-farm-chart/",
                data={"farm_uuid": str(self.farm_uuid), "plant_name": "Tomato"},
                format="json",
            )
            harvest_response = self.client.post(
                "/api/crop-simulation/harvest-prediction/",
                data={"farm_uuid": str(self.farm_uuid), "plant_name": "Tomato"},
                format="json",
            )
            yield_response = self.client.post(
                "/api/crop-simulation/yield-prediction/",
                data={"farm_uuid": str(self.farm_uuid), "plant_name": "Tomato"},
                format="json",
            )
        self.assertEqual(current_chart_response.status_code, 200)
        self.assertEqual(harvest_response.status_code, 200)
        self.assertEqual(yield_response.status_code, 200)

        task_state: dict[str, object] = {}

        def growth_delay_stub(payload):
            serializable_payload = {
                **payload,
                "farm_uuid": str(payload["farm_uuid"]) if payload.get("farm_uuid") else None,
            }
            scenario = SimulationScenario.objects.create(
                name=f"integration-{payload['plant_name']}",
                scenario_type=SimulationScenario.ScenarioType.SINGLE,
                status=SimulationScenario.Status.SUCCESS,
                input_payload=serializable_payload,
                result_payload={"engine": "stub"},
            )
            SimulationRun.objects.create(
                scenario=scenario,
                run_key="primary",
                label=payload["plant_name"],
                status=SimulationScenario.Status.SUCCESS,
                weather_payload=payload.get("weather", []),
                soil_payload=payload.get("soil_parameters", {}),
                result_payload={"summary": "ok"},
            )
            task_id = f"growth-task-{scenario.id}"
            task_state["task_id"] = task_id
            task_state["result"] = {
                "plant_name": payload["plant_name"],
                "dynamic_parameters": payload["dynamic_parameters"],
                "engine": "stub",
                "model_name": "integration-model",
                "scenario_id": scenario.id,
                "simulation_warning": "",
                "summary_metrics": {"yield_estimate": 8.4},
                "stage_timeline": [
                    {
                        "order": 1,
                        "stage_code": "VEG",
                        "stage_name": "Vegetative",
                        "start_date": "2026-04-10",
                        "end_date": "2026-05-05",
                        "days_count": 25,
                        "metrics": {"LAI": {"start": 0.8, "end": 1.5, "min": 0.8, "max": 1.5, "avg": 1.15}},
                    },
                    {
                        "order": 2,
                        "stage_code": "FLOW",
                        "stage_name": "Flowering",
                        "start_date": "2026-05-06",
                        "end_date": "2026-06-01",
                        "days_count": 26,
                        "metrics": {"LAI": {"start": 1.6, "end": 2.2, "min": 1.6, "max": 2.2, "avg": 1.9}},
                    },
                ],
                "daily_records_count": 51,
                "default_page_size": payload.get("page_size", 2),
            }
            return SimpleNamespace(id=task_id)

        with patch("crop_simulation.views.run_growth_simulation_task.delay", side_effect=growth_delay_stub):
            growth_response = self.client.post(
                "/api/crop-simulation/growth/",
                data={
                    "plant_name": "Tomato",
                    "dynamic_parameters": ["DVS", "LAI"],
                    "farm_uuid": str(self.farm_uuid),
                    "page_size": 1,
                },
                format="json",
            )
        self.assertEqual(growth_response.status_code, 202)

        with patch(
            "crop_simulation.views._get_async_result",
            return_value=FakeAsyncResult(state="SUCCESS", result=task_state["result"]),
        ):
            growth_status_response = self.client.get(
                f"/api/crop-simulation/growth/{task_state['task_id']}/status/?page=1&page_size=1"
            )
        self.assertEqual(growth_status_response.status_code, 200)
        self.assertEqual(
            SimulationScenario.objects.filter(name="integration-Tomato").count(),
            1,
        )
        self.assertEqual(SimulationRun.objects.count(), 1)
        self.assertEqual(
            growth_status_response.json()["data"]["result"]["pagination"]["page_size"],
            1,
        )
