from __future__ import annotations

from types import SimpleNamespace
from unittest.mock import Mock, patch

from django.test import SimpleTestCase

from rag.failure_contract import RAGServiceError
from rag.services.pest_disease import get_pest_disease_detection
from rag.services.soil_anomaly import get_soil_anomaly_insight
from rag.services.water_need_prediction import get_water_need_prediction_insight
from rag.services.yield_harvest import YieldHarvestRAGService


class RAGFailureContractTests(SimpleTestCase):
    @patch("rag.services.soil_anomaly._create_audit_log", return_value=object())
    @patch("rag.services.soil_anomaly._fail_audit_log")
    @patch("rag.services.soil_anomaly._build_service_client")
    @patch("rag.services.soil_anomaly.build_rag_context", return_value="")
    @patch("rag.services.soil_anomaly._load_farm_or_error", return_value={"farm_uuid": "farm-1"})
    def test_soil_anomaly_invalid_json_raises_structured_error(
        self,
        _mock_load_farm,
        _mock_context,
        mock_build_client,
        _mock_fail,
        _mock_audit,
    ):
        client = Mock()
        client.chat.completions.create.return_value = SimpleNamespace(
            choices=[SimpleNamespace(message=SimpleNamespace(content="not-json"))]
        )
        mock_build_client.return_value = (SimpleNamespace(system_prompt=""), client, "gpt-test")

        with self.assertRaises(RAGServiceError) as exc_info:
            get_soil_anomaly_insight(farm_uuid="farm-1", anomaly_payload={})

        self.assertEqual(exc_info.exception.contract.error_code, "invalid_json")

    @patch("rag.services.water_need_prediction._create_audit_log", return_value=object())
    @patch("rag.services.water_need_prediction._fail_audit_log")
    @patch("rag.services.water_need_prediction._build_service_client")
    @patch("rag.services.water_need_prediction.build_rag_context", return_value="")
    @patch("rag.services.water_need_prediction._load_farm_or_error", return_value={"farm_uuid": "farm-1"})
    def test_water_need_invalid_json_raises_structured_error(
        self,
        _mock_load_farm,
        _mock_context,
        mock_build_client,
        _mock_fail,
        _mock_audit,
    ):
        client = Mock()
        client.chat.completions.create.return_value = SimpleNamespace(
            choices=[SimpleNamespace(message=SimpleNamespace(content="not-json"))]
        )
        mock_build_client.return_value = (SimpleNamespace(system_prompt=""), client, "gpt-test")

        with self.assertRaises(RAGServiceError) as exc_info:
            get_water_need_prediction_insight(farm_uuid="farm-1", prediction_payload={})

        self.assertEqual(exc_info.exception.contract.error_code, "invalid_json")

    def test_pest_detection_requires_image_with_structured_error(self):
        with self.assertRaises(RAGServiceError) as exc_info:
            get_pest_disease_detection(farm_uuid="farm-1", images=[])

        self.assertEqual(exc_info.exception.contract.error_code, "missing_images")

    @patch("rag.services.yield_harvest._create_audit_log", return_value=object())
    @patch("rag.services.yield_harvest._fail_audit_log")
    @patch("rag.services.yield_harvest.YieldHarvestRAGService._build_service_client")
    def test_yield_harvest_invalid_json_raises_structured_error(
        self,
        mock_build_client,
        _mock_fail,
        _mock_audit,
    ):
        client = Mock()
        client.chat.completions.create.return_value = SimpleNamespace(
            choices=[SimpleNamespace(message=SimpleNamespace(content="not-json"))]
        )
        mock_build_client.return_value = (SimpleNamespace(system_prompt=""), client, "gpt-test")

        with self.assertRaises(RAGServiceError) as exc_info:
            YieldHarvestRAGService().generate_narrative({"farm_uuid": "farm-1"})

        self.assertEqual(exc_info.exception.contract.error_code, "invalid_json")


    @patch("rag.services.soil_anomaly.build_ai_farm_snapshot", return_value={"farm_uuid": "farm-1"})
    def test_soil_anomaly_loads_canonical_snapshot(self, mock_snapshot):
        from rag.services.soil_anomaly import _load_farm_or_error

        payload = _load_farm_or_error("farm-1")

        self.assertEqual(payload["farm_uuid"], "farm-1")
        mock_snapshot.assert_called_once_with("farm-1")

    @patch(
        "rag.services.pest_disease.build_ai_farm_snapshot",
        return_value={
            "farm_uuid": "farm-1",
            "weather": {"forecast": {"humidity_mean": 75.0, "temperature_mean": 31.0, "precipitation": 3.0}},
            "farm_metrics": {"resolved_metrics": {"soil_moisture": 66.0, "electrical_conductivity": 2.8, "soil_ph": 7.9}},
        },
    )
    def test_pest_risk_context_reads_canonical_snapshot_shape(self, mock_snapshot):
        from rag.services.pest_disease import _build_risk_context, _load_farm_or_error

        farm_details = _load_farm_or_error("farm-1")
        risk = _build_risk_context(farm_details, plant_name=None, growth_stage=None)

        self.assertEqual(risk["overall_risk"], "high")
        self.assertIn("EC بالا", risk["key_drivers"])
        mock_snapshot.assert_called_once_with("farm-1")

    @patch("rag.services.pest_disease.build_ai_farm_snapshot", return_value={"farm_uuid": "farm-1"})
    def test_pest_detection_remains_image_first_with_optional_farm_context(self, mock_snapshot):
        with self.assertRaises(RAGServiceError) as exc_info:
            get_pest_disease_detection(farm_uuid="farm-1", images=[])

        self.assertEqual(exc_info.exception.contract.error_code, "missing_images")
        mock_snapshot.assert_not_called()
