"""Smoke tests wiring each endpoint against a FakeProvider (no real API key needed) -
these exist to prove the FastAPI routes, pydantic schemas, and error handling are
wired correctly, not to test any particular LLM's output quality.
"""

from unittest.mock import patch

from fastapi.testclient import TestClient

from app.main import app

client = TestClient(app)


class FakeProvider:
    def __init__(self, result=None, error=None):
        self.result = result
        self.error = error

    def complete_json(self, system_prompt, messages):
        if self.error:
            raise self.error
        return self.result


def test_health():
    response = client.get("/health")
    assert response.status_code == 200
    assert response.json()["status"] == "ok"


def test_generate_itinerary_success():
    fake_result = {
        "options": [
            {
                "letter": "A",
                "name": "Nairobi Wildlife Adventure",
                "is_ai_pick": True,
                "summary": "A wildlife-focused trip.",
                "total_cost": {"amount": 2400, "currency": "USD", "is_estimate": True},
                "days": {
                    "1": {
                        "title": "Arrival",
                        "destinations": {
                            "Nairobi": {
                                "activities": ["evening clubbing", "dinner"],
                                "estimated_cost": {"amount": 80, "currency": "USD", "is_estimate": True},
                            }
                        },
                    }
                },
            }
        ]
    }
    with patch("app.services.itinerary.get_provider", return_value=FakeProvider(result=fake_result)):
        response = client.post(
            "/generate_itinerary",
            json={"trip_brief": "4-day wildlife trip to Kenya", "num_options": 1},
        )
    assert response.status_code == 200
    body = response.json()
    assert body["options"][0]["letter"] == "A"
    assert body["options"][0]["is_ai_pick"] is True
    assert body["options"][0]["days"]["1"]["destinations"]["Nairobi"]["activities"] == [
        "evening clubbing",
        "dinner",
    ]


def test_generate_itinerary_provider_failure_returns_502():
    with patch("app.services.itinerary.get_provider", return_value=FakeProvider(error=RuntimeError("boom"))):
        response = client.post("/generate_itinerary", json={"trip_brief": "trip"})
    assert response.status_code == 502
    assert response.json()["error"]["code"] == "LLM_GENERATION_FAILED"


def test_generate_itinerary_malformed_response_returns_502():
    # missing required "name"/"summary" fields on the option
    with patch("app.services.itinerary.get_provider", return_value=FakeProvider(result={"options": [{"letter": "A"}]})):
        response = client.post("/generate_itinerary", json={"trip_brief": "trip"})
    assert response.status_code == 502
    assert response.json()["error"]["code"] == "LLM_RESPONSE_INVALID"


def test_suggest_questions():
    fake_result = {
        "questions": [
            {"id": "q_01", "text": "What's your budget?", "type": "single_choice", "options": ["Low", "High"]}
        ]
    }
    with patch("app.services.questions.get_provider", return_value=FakeProvider(result=fake_result)):
        response = client.post("/suggest_questions", json={"trip_brief": "Trip to Japan"})
    assert response.status_code == 200
    assert response.json()["questions"][0]["id"] == "q_01"


def test_suggest_response():
    fake_result = {
        "suggestions": [{"text": "Yes, breakfast is included!", "tone": "friendly"}],
        "context_used": ["trip: Santorini"],
    }
    with patch("app.services.reply.get_provider", return_value=FakeProvider(result=fake_result)):
        response = client.post(
            "/suggest_response",
            json={"conversation_history": [{"role": "user", "content": "Does option A include breakfast?"}]},
        )
    assert response.status_code == 200
    assert response.json()["suggestions"][0]["tone"] == "friendly"


def test_suggest_response_collapses_consecutive_same_role_turns():
    from app.schemas import ChatMessage
    from app.services.reply import _to_alternating_messages

    history = [
        ChatMessage(role="user", content="Hi"),
        ChatMessage(role="user", content="Are you there?"),
        ChatMessage(role="assistant", content="Yes!"),
    ]
    merged = _to_alternating_messages(history)
    assert [m["role"] for m in merged] == ["user", "assistant"]
    assert merged[0]["content"] == "Hi\nAre you there?"


def test_recommend_stay_with_candidates_flags_source():
    fake_result = {
        "recommendations": [
            {
                "name": "Caldera View Suites",
                "location": "Santorini",
                "price_per_night": 340,
                "currency": "USD",
                "why": "Matches sea view preference.",
                "is_top_pick": True,
                "source": "search_candidate",
                "source_ref": "ChIJ123",
            }
        ]
    }
    with patch("app.services.stay.get_provider", return_value=FakeProvider(result=fake_result)):
        response = client.post(
            "/recommend_stay",
            json={
                "itinerary": {"1": {"destinations": {"Santorini": {"activities": []}}}},
                "candidates": [{"property_token": "ChIJ123", "name": "Caldera View Suites"}],
            },
        )
    assert response.status_code == 200
    assert response.json()["recommendations"][0]["source"] == "search_candidate"


def test_service_token_required_when_configured():
    with patch("app.main.settings.service_token", "secret123"):
        unauthorized = client.post("/suggest_questions", json={"trip_brief": "Trip"})
        assert unauthorized.status_code == 401

        with patch("app.services.questions.get_provider", return_value=FakeProvider(result={"questions": []})):
            authorized = client.post(
                "/suggest_questions",
                json={"trip_brief": "Trip"},
                headers={"Authorization": "Bearer secret123"},
            )
        assert authorized.status_code == 200
