"""
Integration tests for POST /api/v1/analyze.

Mocks the Celery task dispatch, the app lifespan's heavy startup
dependencies, and the cache layer so these tests verify the HTTP
contract (validation, auth, response shape) without requiring live
Postgres/Redis/Celery/trained models. The actual task execution logic
is covered separately by tests/unit/test_model_runners.py and
tests/unit/test_aggregator.py.

/analyze accepts EITHER an API key (X-API-Key header) OR a logged-in
user (Authorization: Bearer <JWT>) — see app/core/dual_auth.py. Both
paths are tested explicitly here, not just one.
"""

from unittest.mock import AsyncMock, MagicMock, patch

import pytest
from fastapi.testclient import TestClient


@pytest.fixture
def client():
    fake_models = {"sarimax_climate": {}, "xgb_feasibility": {}, "xgb_yield": {},
                    "dt_trend": {}, "rf_recommendation": {}, "sarimax_market": {}}

    with patch("app.models_loader.registry.model_registry.load_all", return_value=None), \
         patch("app.models_loader.registry.model_registry._loaded", True), \
         patch("app.models_loader.registry.model_registry._models", fake_models), \
         patch("app.services.feature_builder.feature_builder.load", return_value=None), \
         patch("app.services.reference_data.reference_data.load", return_value=None), \
         patch("app.services.reference_data.reference_data._loaded", True), \
         patch("app.services.llm_service.llm_service.init", return_value=None), \
         patch("app.services.cache_service.cache_service.init", new=AsyncMock(return_value=None)), \
         patch("app.services.cache_service.cache_service.health_check", new=AsyncMock(return_value=True)), \
         patch("app.services.cache_service.cache_service.get", new=AsyncMock(return_value=None)), \
         patch("app.services.cache_service.cache_service.set", new=AsyncMock(return_value=None)), \
         patch("app.services.cache_service.cache_service._redis", None), \
         patch("app.db.session.check_db_connection", new=AsyncMock(return_value=True)), \
         patch("app.db.session.dispose_engine", new=AsyncMock(return_value=None)):

        from app.main import app
        with TestClient(app) as test_client:
            yield test_client


API_KEY_HEADERS = {"X-API-Key": "dev-local-key-change-me"}
VALID_PAYLOAD = {"crop": "rice", "location": "thanjavur", "month": "June"}


class TestAnalyzeEndpointAuth:
    def test_no_credentials_at_all_returns_401(self, client):
        response = client.post("/api/v1/analyze", json=VALID_PAYLOAD)
        assert response.status_code == 401
        body = response.json()
        assert body["error_code"] == "AUTHENTICATION_FAILED"

    def test_api_key_auth_succeeds(self, client):
        with patch("app.workers.tasks.run_crop_analysis.delay") as mock_delay:
            mock_task = MagicMock()
            mock_task.id = "task-via-api-key"
            mock_delay.return_value = mock_task

            response = client.post("/api/v1/analyze", json=VALID_PAYLOAD, headers=API_KEY_HEADERS)

            assert response.status_code == 202
            _, kwargs = mock_delay.call_args
            assert kwargs["user_id"] is None
            assert kwargs["api_key_prefix"] is not None

    def test_invalid_bearer_token_returns_401(self, client):
        response = client.post(
            "/api/v1/analyze",
            json=VALID_PAYLOAD,
            headers={"Authorization": "Bearer not-a-real-token"},
        )
        assert response.status_code == 401


class TestAnalyzeEndpointValidation:
    def test_invalid_month_name_returns_422(self, client):
        response = client.post(
            "/api/v1/analyze",
            json={"crop": "rice", "location": "thanjavur", "month": "Smarch"},
            headers=API_KEY_HEADERS,
        )
        assert response.status_code == 422
        body = response.json()
        assert body["error_code"] == "VALIDATION_FAILED"
        assert "request_id" in body

    def test_missing_crop_field_returns_422(self, client):
        response = client.post(
            "/api/v1/analyze",
            json={"location": "thanjavur", "month": 6},
            headers=API_KEY_HEADERS,
        )
        assert response.status_code == 422

    @patch("app.workers.tasks.run_crop_analysis.delay")
    def test_valid_request_queues_task_and_returns_202(self, mock_delay, client):
        mock_task = MagicMock()
        mock_task.id = "fake-task-id-123"
        mock_delay.return_value = mock_task

        response = client.post(
            "/api/v1/analyze",
            json=VALID_PAYLOAD,
            headers=API_KEY_HEADERS,
        )

        assert response.status_code == 202
        body = response.json()
        assert body["task_id"] == "fake-task-id-123"
        assert body["status"] == "queued"
        assert "poll_url" in body
