from datetime import date, timedelta
import uuid
from unittest.mock import patch

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

from location_data.models import (
    AnalysisGridCell,
    AnalysisGridObservation,
    BlockSubdivision,
    RemoteSensingClusterBlock,
    RemoteSensingRun,
    RemoteSensingSubdivisionResult,
    SoilLocation,
)


@override_settings(ROOT_URLCONF="location_data.urls")
class RemoteSensingClusterBlockLiveApiTests(TestCase):
    def setUp(self):
        self.client = APIClient()
        self.boundary = {
            "type": "Polygon",
            "coordinates": [
                [
                    [51.3890, 35.6890],
                    [51.3900, 35.6890],
                    [51.3900, 35.6900],
                    [51.3890, 35.6900],
                    [51.3890, 35.6890],
                ]
            ],
        }
        self.location = SoilLocation.objects.create(
            latitude="35.689200",
            longitude="51.389000",
            farm_boundary=self.boundary,
        )
        self.subdivision = BlockSubdivision.objects.create(
            soil_location=self.location,
            block_code="block-1",
            source_boundary=self.boundary,
            chunk_size_sqm=900,
            status="subdivided",
        )
        self.run = RemoteSensingRun.objects.create(
            soil_location=self.location,
            block_subdivision=self.subdivision,
            block_code="block-1",
            chunk_size_sqm=900,
            temporal_start=date(2025, 1, 1),
            temporal_end=date(2025, 1, 31),
            status=RemoteSensingRun.STATUS_SUCCESS,
            metadata={"stage": "completed"},
        )
        self.result = RemoteSensingSubdivisionResult.objects.create(
            soil_location=self.location,
            run=self.run,
            block_subdivision=self.subdivision,
            block_code="block-1",
            chunk_size_sqm=900,
            temporal_start=date(2025, 1, 1),
            temporal_end=date(2025, 1, 31),
            cluster_count=1,
            selected_features=["ndvi", "ndwi", "soil_vv_db"],
            metadata={"used_cell_count": 2, "skipped_cell_count": 0},
        )
        self.cluster_block = RemoteSensingClusterBlock.objects.create(
            result=self.result,
            soil_location=self.location,
            block_subdivision=self.subdivision,
            block_code="block-1",
            sub_block_code="cluster-0",
            cluster_label=0,
            chunk_size_sqm=900,
            centroid_lat="35.689500",
            centroid_lon="51.389500",
            cell_count=2,
            cell_codes=["cell-1", "cell-2"],
            geometry=self.boundary,
            metadata={"source": "analysis_grid_cells"},
        )

    def test_get_cluster_block_live_returns_404_when_uuid_missing(self):
        response = self.client.get(
            f"/remote-sensing/cluster-blocks/{uuid.uuid4()}/live/"
        )

        self.assertEqual(response.status_code, 404)
        self.assertEqual(response.json()["msg"], "زیر‌بلاک KMeans پیدا نشد.")

    @patch("location_data.views.compute_remote_sensing_metrics")
    def test_get_cluster_block_live_reads_fresh_metrics_from_openeo(self, compute_mock):
        compute_mock.return_value = {
            "results": {
                f"cluster-{self.cluster_block.uuid}": {
                    "ndvi": 0.63,
                    "ndwi": 0.21,
                    "soil_vv": 0.13,
                    "soil_vv_db": -8.860566,
                }
            },
            "metadata": {
                "backend": "openeo",
                "backend_url": "https://openeofed.dataspace.copernicus.eu",
                "collections_used": ["SENTINEL2_L2A", "SENTINEL1_GRD"],
                "job_refs": {"ndvi": "job-1", "ndwi": "job-2", "soil_vv": "job-3"},
                "failed_metrics": [],
                "payload_diagnostics": {},
            },
        }

        response = self.client.get(
            f"/remote-sensing/cluster-blocks/{self.cluster_block.uuid}/live/",
            data={"temporal_start": "2025-02-01", "temporal_end": "2025-02-15"},
        )

        self.assertEqual(response.status_code, 200)
        payload = response.json()["data"]
        self.assertEqual(payload["status"], "success")
        self.assertEqual(payload["source"], "openeo")
        self.assertEqual(payload["cluster_block"]["uuid"], str(self.cluster_block.uuid))
        self.assertEqual(payload["summary"]["cell_count"], 2)
        self.assertEqual(payload["summary"]["ndvi_mean"], 0.63)
        self.assertEqual(payload["metrics"]["soil_vv_db"], -8.860566)
        self.assertEqual(payload["temporal_extent"]["start_date"], "2025-02-01")
        self.assertEqual(payload["temporal_extent"]["end_date"], "2025-02-15")
        self.assertEqual(
            payload["metadata"]["requested_cluster_uuid"],
            str(self.cluster_block.uuid),
        )

        compute_mock.assert_called_once()
        args, kwargs = compute_mock.call_args
        self.assertEqual(len(args[0]), 1)
        self.assertEqual(args[0][0].geometry, self.boundary)
        self.assertEqual(kwargs["temporal_start"].isoformat(), "2025-02-01")
        self.assertEqual(kwargs["temporal_end"].isoformat(), "2025-02-15")

    @patch("location_data.views.compute_remote_sensing_metrics")
    def test_get_cluster_block_live_rebuilds_geometry_from_member_cells_when_missing(self, compute_mock):
        cell_geometry = {
            "type": "Polygon",
            "coordinates": [
                [
                    [51.3890, 35.6890],
                    [51.3895, 35.6890],
                    [51.3895, 35.6895],
                    [51.3890, 35.6895],
                    [51.3890, 35.6890],
                ]
            ],
        }
        AnalysisGridCell.objects.create(
            soil_location=self.location,
            block_subdivision=self.subdivision,
            block_code="block-1",
            cell_code="cell-1",
            chunk_size_sqm=900,
            geometry=cell_geometry,
            centroid_lat="35.689250",
            centroid_lon="51.389250",
        )
        self.cluster_block.geometry = {}
        self.cluster_block.save(update_fields=["geometry", "updated_at"])
        compute_mock.return_value = {
            "results": {
                f"cluster-{self.cluster_block.uuid}": {
                    "ndvi": 0.55,
                    "ndwi": 0.18,
                    "soil_vv": 0.10,
                    "soil_vv_db": -10.0,
                }
            },
            "metadata": {"job_refs": {}, "failed_metrics": [], "payload_diagnostics": {}},
        }

        response = self.client.get(
            f"/remote-sensing/cluster-blocks/{self.cluster_block.uuid}/live/",
            data={"days": 7},
        )

        self.assertEqual(response.status_code, 200)
        payload = response.json()["data"]
        self.assertEqual(payload["metrics"]["ndvi"], 0.55)

        compute_mock.assert_called_once()
        args, kwargs = compute_mock.call_args
        self.assertEqual(args[0][0].geometry["type"], "Polygon")
        self.assertEqual(args[0][0].geometry, cell_geometry)
        expected_end = timezone.localdate() - timedelta(days=1)
        expected_start = expected_end - timedelta(days=6)
        self.assertEqual(kwargs["temporal_start"], expected_start)
        self.assertEqual(kwargs["temporal_end"], expected_end)

    @patch("location_data.views.compute_remote_sensing_metrics")
    def test_get_cluster_block_live_uses_database_cache_for_matching_window(self, compute_mock):
        cell_1 = AnalysisGridCell.objects.create(
            soil_location=self.location,
            block_subdivision=self.subdivision,
            block_code="block-1",
            cell_code="cell-1",
            chunk_size_sqm=900,
            geometry=self.boundary,
            centroid_lat="35.689250",
            centroid_lon="51.389250",
        )
        cell_2 = AnalysisGridCell.objects.create(
            soil_location=self.location,
            block_subdivision=self.subdivision,
            block_code="block-1",
            cell_code="cell-2",
            chunk_size_sqm=900,
            geometry=self.boundary,
            centroid_lat="35.689750",
            centroid_lon="51.389750",
        )
        AnalysisGridObservation.objects.create(
            cell=cell_1,
            run=self.run,
            temporal_start=date(2025, 1, 1),
            temporal_end=date(2025, 1, 31),
            ndvi=0.44,
            ndwi=0.12,
            soil_vv=0.09,
            soil_vv_db=-11.0,
            metadata={"backend_name": "openeo"},
        )
        AnalysisGridObservation.objects.create(
            cell=cell_2,
            run=self.run,
            temporal_start=date(2025, 1, 1),
            temporal_end=date(2025, 1, 31),
            ndvi=0.64,
            ndwi=0.22,
            soil_vv=0.19,
            soil_vv_db=-7.0,
            metadata={"backend_name": "openeo"},
        )

        response = self.client.get(
            f"/remote-sensing/cluster-blocks/{self.cluster_block.uuid}/live/",
            data={"temporal_start": "2025-01-01", "temporal_end": "2025-01-31"},
        )

        self.assertEqual(response.status_code, 200)
        payload = response.json()["data"]
        self.assertEqual(payload["source"], "database")
        self.assertTrue(payload["metadata"]["cache_hit"])
        self.assertEqual(payload["summary"]["ndvi_mean"], 0.54)
        self.assertEqual(payload["metrics"]["soil_vv_db"], -9.0)
        compute_mock.assert_not_called()
