from __future__ import annotations

from types import SimpleNamespace
from unittest.mock import patch

from django.test import TestCase, override_settings
from rest_framework.test import APIClient

from farm_data.models import PlantCatalogSnapshot

from .growth_simulation import paginate_growth_stages, run_growth_simulation


@override_settings(ROOT_URLCONF="crop_simulation.urls")
class PlantGrowthSimulationApiTests(TestCase):
    def setUp(self):
        self.client = APIClient()
        self.plant = PlantCatalogSnapshot.objects.create(
            backend_plant_id=301,
            name="گوجه‌فرنگی",
            growth_profile={
                "base_temperature": 10,
                "required_gdd_for_maturity": 1200,
                "current_cumulative_gdd": 50,
            },
        )
        self.weather = [
            {
                "DAY": "2026-04-01",
                "LAT": 35.7,
                "LON": 51.4,
                "TMIN": 12,
                "TMAX": 24,
                "RAIN": 0.0,
                "ET0": 0.32,
            },
            {
                "DAY": "2026-04-02",
                "LAT": 35.7,
                "LON": 51.4,
                "TMIN": 13,
                "TMAX": 25,
                "RAIN": 0.0,
                "ET0": 0.34,
            },
            {
                "DAY": "2026-04-03",
                "LAT": 35.7,
                "LON": 51.4,
                "TMIN": 14,
                "TMAX": 27,
                "RAIN": 1.0,
                "ET0": 0.36,
            },
        ]

    def test_run_growth_simulation_returns_stage_timeline(self):
        with patch("crop_simulation.growth_simulation._run_simulation") as mock_run_simulation:
            mock_run_simulation.return_value = (
                {
                    "engine": "pcse",
                    "model_name": "wofost",
                    "metrics": {"yield_estimate": 10.0},
                    "daily_output": [
                        {"DAY": "2026-04-01", "DVS": 0.1, "LAI": 0.2, "TAGP": 10.0},
                        {"DAY": "2026-04-02", "DVS": 0.3, "LAI": 0.4, "TAGP": 20.0},
                        {"DAY": "2026-04-03", "DVS": 1.1, "LAI": 0.6, "TAGP": 30.0},
                    ],
                },
                12,
                None,
            )
            result = run_growth_simulation(
                {
                    "plant_name": self.plant.name,
                    "dynamic_parameters": ["DVS", "LAI", "TAGP"],
                    "weather": self.weather,
                    "soil_parameters": {"SMFCF": 0.34, "SMW": 0.14, "RDMSOL": 120.0},
                    "site_parameters": {"WAV": 40.0},
                    "irrigation_recommendation": {"events": [{"date": "2026-04-02", "amount": 2.5}]},
                    "fertilization_recommendation": {
                        "events": [{"date": "2026-04-02", "N_amount": 45, "N_recovery": 0.7}]
                    },
                    "page_size": 2,
                }
            )

        self.assertEqual(result["plant_name"], self.plant.name)
        self.assertGreaterEqual(result["daily_records_count"], 3)
        self.assertTrue(result["stage_timeline"])
        self.assertEqual(result["pagination"]["page_size"], 2)

    @patch("crop_simulation.views.run_growth_simulation_task.delay")
    def test_queue_api_returns_task_id(self, mock_delay):
        mock_delay.return_value = SimpleNamespace(id="growth-task-1")

        response = self.client.post(
            "/growth/",
            data={
                "plant_name": self.plant.name,
                "dynamic_parameters": ["DVS", "LAI"],
                "weather": self.weather,
                "irrigation_recommendation": {"events": [{"date": "2026-04-02", "amount": 2.5}]},
                "fertilization_recommendation": {
                    "events": [{"date": "2026-04-02", "N_amount": 45, "N_recovery": 0.7}]
                },
            },
            format="json",
        )

        self.assertEqual(response.status_code, 202)
        self.assertEqual(response.json()["data"]["task_id"], "growth-task-1")
        self.assertEqual(mock_delay.call_args.args[0]["irrigation_recommendation"]["events"][0]["amount"], 2.5)

    @patch("crop_simulation.views.run_growth_simulation_task.delay")
    def test_queue_api_accepts_crop_alias(self, mock_delay):
        mock_delay.return_value = SimpleNamespace(id="growth-task-2")

        response = self.client.post(
            "/growth/",
            data={
                "crop": "tomato",
                "dynamic_parameters": ["DVS", "LAI"],
                "weather": self.weather,
            },
            format="json",
        )

        self.assertEqual(response.status_code, 202)
        self.assertEqual(mock_delay.call_args.args[0]["plant_name"], self.plant.name)
        self.assertEqual(response.json()["data"]["plant_name"], self.plant.name)

    def test_queue_api_returns_400_for_missing_weather_and_farm_uuid(self):
        response = self.client.post(
            "/growth/",
            data={
                "plant_name": self.plant.name,
                "dynamic_parameters": ["DVS", "LAI"],
            },
            format="json",
        )

        self.assertEqual(response.status_code, 400)
        self.assertEqual(response.json()["code"], 400)

    @patch("crop_simulation.views._get_async_result")
    def test_status_api_returns_paginated_stages(self, mock_get_async_result):
        stage_timeline = [
            {
                "order": 1,
                "stage_code": "establishment",
                "stage_name": "استقرار",
                "start_date": "2026-04-01",
                "end_date": "2026-04-02",
                "days_count": 2,
                "metrics": {"DVS": {"start": 0.1, "end": 0.2, "min": 0.1, "max": 0.2, "avg": 0.15}},
            },
            {
                "order": 2,
                "stage_code": "vegetative",
                "stage_name": "رشد رویشی",
                "start_date": "2026-04-03",
                "end_date": "2026-04-05",
                "days_count": 3,
                "metrics": {"DVS": {"start": 0.3, "end": 0.8, "min": 0.3, "max": 0.8, "avg": 0.55}},
            },
            {
                "order": 3,
                "stage_code": "flowering",
                "stage_name": "گلدهی",
                "start_date": "2026-04-06",
                "end_date": "2026-04-07",
                "days_count": 2,
                "metrics": {"DVS": {"start": 1.0, "end": 1.2, "min": 1.0, "max": 1.2, "avg": 1.1}},
            },
        ]
        mock_get_async_result.return_value = SimpleNamespace(
            state="SUCCESS",
            result={
                "plant_name": self.plant.name,
                "dynamic_parameters": ["DVS"],
                "engine": "growth_projection",
                "model_name": "growth_projection_v1",
                "scenario_id": None,
                "simulation_warning": None,
                "summary_metrics": {},
                "stage_timeline": stage_timeline,
                "stages_page": stage_timeline[:1],
                "pagination": paginate_growth_stages(stage_timeline, page=1, page_size=1)["pagination"],
                "daily_records_count": 7,
                "default_page_size": 1,
            },
        )

        response = self.client.get("/growth/growth-task-1/status/?page=2&page_size=1")

        self.assertEqual(response.status_code, 200)
        payload = response.json()["data"]["result"]
        self.assertEqual(payload["pagination"]["page"], 2)
        self.assertEqual(len(payload["stages_page"]), 1)
        self.assertEqual(payload["stages_page"][0]["stage_code"], "vegetative")

    @patch("crop_simulation.views._get_async_result")
    def test_status_api_returns_pending_state(self, mock_get_async_result):
        mock_get_async_result.return_value = SimpleNamespace(state="PENDING")

        response = self.client.get("/growth/growth-task-1/status/")

        self.assertEqual(response.status_code, 200)
        payload = response.json()["data"]
        self.assertEqual(payload["status"], "PENDING")
        self.assertIn("message", payload)

    @patch("crop_simulation.views._get_async_result")
    def test_status_api_returns_failure_state(self, mock_get_async_result):
        mock_get_async_result.return_value = SimpleNamespace(
            state="FAILURE",
            result=RuntimeError("task crashed"),
        )

        response = self.client.get("/growth/growth-task-1/status/")

        self.assertEqual(response.status_code, 200)
        payload = response.json()["data"]
        self.assertEqual(payload["status"], "FAILURE")
        self.assertEqual(payload["error"], "task crashed")

    @patch("crop_simulation.views.apps.get_app_config")
    def test_current_farm_chart_api_returns_simulation_payload(self, mock_get_app_config):
        mock_simulator = SimpleNamespace(
            simulate=lambda **_kwargs: {
                "farm_uuid": "550e8400-e29b-41d4-a716-446655440000",
                "plant_name": self.plant.name,
                "engine": "growth_projection",
                "model_name": "growth_projection_v1",
                "scenario_id": 12,
                "simulation_warning": None,
                "categories": ["2026-04-01", "2026-04-02"],
                "series": [
                    {"name": "تعداد برگ تخمینی", "key": "leaf_count_estimate", "data": [120.0, 140.0]},
                    {"name": "وزن بیوماس", "key": "biomass_weight", "data": [35.0, 45.0]},
                ],
                "summary": [
                    {
                        "title": "تعداد برگ تخمینی",
                        "subtitle": "وضعیت فعلی",
                        "amount": 140.0,
                        "unit": "leaf",
                        "avatarColor": "success",
                        "avatarIcon": "tabler-leaf",
                    }
                ],
                "current_state": {
                    "date": "2026-04-02",
                    "leaf_count_estimate": 140.0,
                    "leaf_area_index": 0.0117,
                    "biomass_weight": 45.0,
                    "storage_organ_weight": 10.0,
                    "soil_moisture_percent": 41.2,
                    "development_stage": 0.35,
                    "gdd": 9.0,
                },
                "metrics": {"yield_estimate": 10.0},
                "daily_output": [],
            }
        )
        mock_get_app_config.return_value = SimpleNamespace(
            get_current_farm_chart_simulator=lambda: mock_simulator
        )

        response = self.client.post(
            "/current-farm-chart/",
            data={
                "farm_uuid": "550e8400-e29b-41d4-a716-446655440000",
                "plant_name": self.plant.name,
            },
            format="json",
        )

        self.assertEqual(response.status_code, 200)
        payload = response.json()["data"]
        self.assertEqual(payload["plant_name"], self.plant.name)
        self.assertEqual(payload["scenario_id"], 12)
        self.assertEqual(payload["current_state"]["leaf_count_estimate"], 140.0)
        self.assertEqual(payload["series"][0]["key"], "leaf_count_estimate")

    def test_current_farm_chart_api_returns_400_for_missing_farm_uuid(self):
        response = self.client.post(
            "/current-farm-chart/",
            data={"plant_name": self.plant.name},
            format="json",
        )

        self.assertEqual(response.status_code, 400)
        self.assertEqual(response.json()["code"], 400)

    @patch("crop_simulation.views.apps.get_app_config")
    def test_current_farm_chart_api_accepts_crop_name_alias(self, mock_get_app_config):
        captured = {}

        def simulate(**kwargs):
            captured.update(kwargs)
            return {
                "farm_uuid": kwargs["farm_uuid"],
                "plant_name": kwargs["plant_name"],
                "engine": "growth_projection",
                "model_name": "growth_projection_v1",
                "scenario_id": 12,
                "simulation_warning": None,
                "categories": [],
                "series": [],
                "summary": [],
                "current_state": {},
                "metrics": {},
                "daily_output": [],
            }

        mock_get_app_config.return_value = SimpleNamespace(
            get_current_farm_chart_simulator=lambda: SimpleNamespace(simulate=simulate)
        )

        response = self.client.post(
            "/current-farm-chart/",
            data={
                "farm_uuid": "550e8400-e29b-41d4-a716-446655440000",
                "crop_name": "tomato",
            },
            format="json",
        )

        self.assertEqual(response.status_code, 200)
        self.assertEqual(captured["plant_name"], self.plant.name)
        self.assertEqual(response.json()["data"]["plant_name"], self.plant.name)

    @patch("crop_simulation.views.apps.get_app_config")
    def test_current_farm_chart_api_returns_500_when_simulator_fails(self, mock_get_app_config):
        mock_simulator = SimpleNamespace(
            simulate=lambda **_kwargs: (_ for _ in ()).throw(RuntimeError("simulator offline"))
        )
        mock_get_app_config.return_value = SimpleNamespace(
            get_current_farm_chart_simulator=lambda: mock_simulator
        )

        response = self.client.post(
            "/current-farm-chart/",
            data={
                "farm_uuid": "550e8400-e29b-41d4-a716-446655440000",
                "plant_name": self.plant.name,
            },
            format="json",
        )

        self.assertEqual(response.status_code, 500)
        self.assertEqual(response.json()["code"], 500)

    @patch("crop_simulation.views.apps.get_app_config")
    def test_harvest_prediction_api_returns_payload(self, mock_get_app_config):
        mock_service = SimpleNamespace(
            get_harvest_prediction=lambda **_kwargs: {
                "date": "2026-05-14",
                "dateFormatted": "14 May 2026",
                "daysUntil": 43,
                "description": "شبيه ساز نشان مي دهد حدود 43 روز ديگر تا برداشت باقي مانده است.",
                "optimalWindowStart": "2026-05-11",
                "optimalWindowEnd": "2026-05-17",
                "gddDetails": {
                    "current_cumulative_gdd": 50.0,
                    "required_gdd_for_maturity": 1200.0,
                    "remaining_gdd": 1150.0,
                    "simulation_engine": "growth_projection",
                },
            }
        )
        mock_get_app_config.return_value = SimpleNamespace(
            get_harvest_prediction_service=lambda: mock_service
        )

        response = self.client.post(
            "/harvest-prediction/",
            data={
                "farm_uuid": "550e8400-e29b-41d4-a716-446655440000",
                "plant_name": self.plant.name,
            },
            format="json",
        )

        self.assertEqual(response.status_code, 200)
        payload = response.json()["data"]
        self.assertEqual(payload["daysUntil"], 43)
        self.assertEqual(payload["gddDetails"]["simulation_engine"], "growth_projection")

    def test_harvest_prediction_api_returns_400_for_missing_farm_uuid(self):
        response = self.client.post(
            "/harvest-prediction/",
            data={"plant_name": self.plant.name},
            format="json",
        )

        self.assertEqual(response.status_code, 400)
        self.assertEqual(response.json()["code"], 400)

    @patch("crop_simulation.views.apps.get_app_config")
    def test_harvest_prediction_api_accepts_crop_alias(self, mock_get_app_config):
        captured = {}

        def get_harvest_prediction(**kwargs):
            captured.update(kwargs)
            return {
                "date": "2026-05-14",
                "dateFormatted": "14 May 2026",
                "daysUntil": 43,
                "description": "ok",
                "optimalWindowStart": "2026-05-11",
                "optimalWindowEnd": "2026-05-17",
                "gddDetails": {},
            }

        mock_get_app_config.return_value = SimpleNamespace(
            get_harvest_prediction_service=lambda: SimpleNamespace(
                get_harvest_prediction=get_harvest_prediction
            )
        )

        response = self.client.post(
            "/harvest-prediction/",
            data={
                "farm_uuid": "550e8400-e29b-41d4-a716-446655440000",
                "crop": "tomato",
            },
            format="json",
        )

        self.assertEqual(response.status_code, 200)
        self.assertEqual(captured["plant_name"], self.plant.name)

    @patch("crop_simulation.views.apps.get_app_config")
    def test_harvest_prediction_api_returns_500_when_service_fails(self, mock_get_app_config):
        class BrokenService:
            def get_harvest_prediction(self, **_kwargs):
                raise RuntimeError("harvest offline")

        mock_get_app_config.return_value = SimpleNamespace(
            get_harvest_prediction_service=lambda: BrokenService()
        )

        response = self.client.post(
            "/harvest-prediction/",
            data={
                "farm_uuid": "550e8400-e29b-41d4-a716-446655440000",
                "plant_name": self.plant.name,
            },
            format="json",
        )

        self.assertEqual(response.status_code, 500)
        self.assertEqual(response.json()["code"], 500)

    @patch("crop_simulation.views.apps.get_app_config")
    def test_yield_prediction_api_returns_payload(self, mock_get_app_config):
        mock_service = SimpleNamespace(
            get_yield_prediction=lambda **_kwargs: {
                "farm_uuid": "550e8400-e29b-41d4-a716-446655440000",
                "plant_name": self.plant.name,
                "predictedYieldTons": 5.4,
                "predictedYieldRaw": 5400.0,
                "unit": "تن",
                "sourceUnit": "kg/ha",
                "simulationEngine": "growth_projection",
                "simulationModel": "growth_projection_v1",
                "scenarioId": 12,
                "simulationWarning": None,
                "supportingMetrics": {"yield_estimate": 5400.0},
            }
        )
        mock_get_app_config.return_value = SimpleNamespace(
            get_yield_prediction_service=lambda: mock_service
        )

        response = self.client.post(
            "/yield-prediction/",
            data={
                "farm_uuid": "550e8400-e29b-41d4-a716-446655440000",
                "plant_name": self.plant.name,
            },
            format="json",
        )

        self.assertEqual(response.status_code, 200)
        payload = response.json()["data"]
        self.assertEqual(payload["predictedYieldTons"], 5.4)
        self.assertEqual(payload["sourceUnit"], "kg/ha")

    def test_yield_prediction_api_returns_400_for_missing_farm_uuid(self):
        response = self.client.post(
            "/yield-prediction/",
            data={"plant_name": self.plant.name},
            format="json",
        )

        self.assertEqual(response.status_code, 400)
        self.assertEqual(response.json()["code"], 400)

    @patch("crop_simulation.views.apps.get_app_config")
    def test_yield_prediction_api_accepts_crop_alias(self, mock_get_app_config):
        captured = {}

        def get_yield_prediction(**kwargs):
            captured.update(kwargs)
            return {
                "farm_uuid": kwargs["farm_uuid"],
                "plant_name": kwargs["plant_name"],
                "predictedYieldTons": 5.4,
                "predictedYieldRaw": 5400.0,
                "unit": "تن",
                "sourceUnit": "kg/ha",
                "simulationEngine": "growth_projection",
                "simulationModel": "growth_projection_v1",
                "scenarioId": 12,
                "simulationWarning": None,
                "supportingMetrics": {},
            }

        mock_get_app_config.return_value = SimpleNamespace(
            get_yield_prediction_service=lambda: SimpleNamespace(
                get_yield_prediction=get_yield_prediction
            )
        )

        response = self.client.post(
            "/yield-prediction/",
            data={
                "farm_uuid": "550e8400-e29b-41d4-a716-446655440000",
                "crop": "tomato",
            },
            format="json",
        )

        self.assertEqual(response.status_code, 200)
        self.assertEqual(captured["plant_name"], self.plant.name)

    @patch("crop_simulation.views.apps.get_app_config")
    def test_yield_prediction_api_returns_500_when_service_fails(self, mock_get_app_config):
        class BrokenService:
            def get_yield_prediction(self, **_kwargs):
                raise RuntimeError("yield offline")

        mock_get_app_config.return_value = SimpleNamespace(
            get_yield_prediction_service=lambda: BrokenService()
        )

        response = self.client.post(
            "/yield-prediction/",
            data={
                "farm_uuid": "550e8400-e29b-41d4-a716-446655440000",
                "plant_name": self.plant.name,
            },
            format="json",
        )

        self.assertEqual(response.status_code, 500)
        self.assertEqual(response.json()["code"], 500)

    @patch("crop_simulation.views.YieldHarvestSummaryService")
    def test_yield_harvest_summary_api_returns_payload(self, mock_service_cls):
        mock_service_cls.return_value.get_summary.return_value = {
            "farm_uuid": "550e8400-e29b-41d4-a716-446655440000",
            "season_highlights_card": {"title": "Season highlights", "subtitle": "Good season."},
            "yield_prediction": {"predicted_yield_tons": 5.4, "explanation": "Stable projection."},
            "harvest_prediction_card": {"harvest_date": "2026-05-14"},
            "harvest_readiness_zones": {"averageReadiness": 74, "summary": "Readiness improving."},
            "yield_quality_bands": {"primary_quality_grade": "A"},
            "harvest_operations_card": {"steps": [{"key": "harvesting", "note": "Prepare combine."}]},
            "yield_prediction_chart": {"series": [], "xAxis": {"type": "datetime"}},
        }

        response = self.client.get(
            "/yield-harvest-summary/?farm_uuid=550e8400-e29b-41d4-a716-446655440000"
            "&season_year=1404&crop_name=wheat&include_narrative=true"
            "&irrigation_recommendation=%7B%22events%22%3A%5B%7B%22date%22%3A%222026-04-25%22%2C%22amount%22%3A2.5%7D%5D%7D"
            "&fertilization_recommendation=%7B%22events%22%3A%5B%7B%22date%22%3A%222026-04-20%22%2C%22N_amount%22%3A45%2C%22N_recovery%22%3A0.7%7D%5D%7D"
        )

        self.assertEqual(response.status_code, 200)
        payload = response.json()["data"]
        self.assertEqual(payload["farm_uuid"], "550e8400-e29b-41d4-a716-446655440000")
        self.assertEqual(payload["yield_quality_bands"]["primary_quality_grade"], "A")
        mock_service_cls.return_value.get_summary.assert_called_once_with(
            farm_uuid="550e8400-e29b-41d4-a716-446655440000",
            season_year="1404",
            crop_name="wheat",
            include_narrative=True,
            irrigation_recommendation={
                "events": [
                    {
                        "date": "2026-04-25",
                        "amount": 2.5,
                    }
                ]
            },
            fertilization_recommendation={
                "events": [
                    {
                        "date": "2026-04-20",
                        "N_amount": 45,
                        "N_recovery": 0.7,
                    }
                ]
            },
        )

    def test_yield_harvest_summary_api_returns_400_for_missing_farm_uuid(self):
        response = self.client.get("/yield-harvest-summary/?season_year=1404&crop_name=wheat")

        self.assertEqual(response.status_code, 400)
        self.assertEqual(response.json()["code"], 400)

    def test_yield_harvest_summary_api_returns_400_for_invalid_json_recommendations(self):
        response = self.client.get(
            "/yield-harvest-summary/?farm_uuid=550e8400-e29b-41d4-a716-446655440000"
            "&irrigation_recommendation=%7Binvalid-json%7D"
        )

        self.assertEqual(response.status_code, 400)
        self.assertEqual(response.json()["code"], 400)

    @patch("crop_simulation.views.YieldHarvestSummaryService")
    def test_yield_harvest_summary_api_accepts_plant_name_alias(self, mock_service_cls):
        mock_service_cls.return_value.get_summary.return_value = {
            "farm_uuid": "550e8400-e29b-41d4-a716-446655440000",
            "season_highlights_card": {},
            "yield_prediction": {},
            "harvest_prediction_card": {},
            "harvest_readiness_zones": {},
            "yield_quality_bands": {},
            "harvest_operations_card": {},
            "yield_prediction_chart": {},
        }

        response = self.client.get(
            "/yield-harvest-summary/?farm_uuid=550e8400-e29b-41d4-a716-446655440000&plant_name=tomato"
        )

        self.assertEqual(response.status_code, 200)
        self.assertEqual(
            mock_service_cls.return_value.get_summary.call_args.kwargs["crop_name"],
            self.plant.name,
        )
