import os

import pytest
from fastapi.testclient import TestClient


os.environ["LLM_MODE"] = "mock"

from app.main import app  # noqa: E402


client = TestClient(app)


def preferences():
    return {
        "destination": "深圳市光明区",
        "themes": ["亲子"],
        "pace": "moderate",
        "interests": ["自然风光"],
        "transport": "public_transport",
    }


def candidates(count: int = 9):
    return [
        {
            "id": f"poi_candidate_{index}",
            "name": f"候选点位 {index}",
            "categoryCode": "urban_park" if index % 2 else "cultural_venue",
            "tagCodes": ["family_friendly", "nature_view"],
            "summary": "仅用于接口契约测试。",
            "recommendationIndex": 1 + index % 5,
        }
        for index in range(1, count + 1)
    ]


def request_payload(**overrides):
    payload = {
        "mode": "quick",
        "preferences": preferences(),
        "durationMinutes": 240,
        "selectedPoiIds": [],
        "candidates": candidates(),
    }
    payload.update(overrides)
    return payload


def test_health_distinguishes_mock_and_configuration():
    response = client.get("/api/v1/health")
    assert response.status_code == 200
    data = response.json()
    assert data["status"] == "ok"
    assert data["mode"] == "mock"
    assert data["ready"] is True
    assert isinstance(data["configured"], bool)


def test_quick_recommendations_only_use_candidate_whitelist():
    payload = request_payload(durationMinutes=480)
    response = client.post("/api/v1/plans", json=payload)
    assert response.status_code == 200
    data = response.json()
    allowed_ids = {candidate["id"] for candidate in payload["candidates"]}
    ids = [item["poiId"] for item in data["recommendations"]]
    assert 3 <= len(ids) <= 8
    assert len(ids) == len(set(ids))
    assert set(ids) <= allowed_ids
    assert all("longitude" not in item for item in data["recommendations"])


def test_custom_keeps_every_selected_poi_even_when_duration_is_short():
    selected = [f"poi_candidate_{index}" for index in range(1, 7)]
    response = client.post(
        "/api/v1/plans",
        json=request_payload(
            mode="custom",
            durationMinutes=120,
            selectedPoiIds=selected,
        ),
    )
    assert response.status_code == 200
    data = response.json()
    ids = [item["poiId"] for item in data["recommendations"]]
    assert ids == selected
    assert data["mode"] == "custom"


@pytest.mark.parametrize(
    "selected",
    [
        ["poi_candidate_1"],
        [f"poi_candidate_{index}" for index in range(1, 10)],
    ],
)
def test_custom_requires_two_to_eight_pois(selected):
    response = client.post(
        "/api/v1/plans",
        json=request_payload(mode="custom", selectedPoiIds=selected),
    )
    assert response.status_code == 422


def test_unknown_selected_id_is_rejected():
    response = client.post(
        "/api/v1/plans",
        json=request_payload(
            mode="custom",
            selectedPoiIds=["poi_candidate_1", "poi_not_in_whitelist"],
        ),
    )
    assert response.status_code == 422


@pytest.mark.parametrize("duration", [90, 125, 630])
def test_duration_must_use_supported_range_and_step(duration):
    response = client.post(
        "/api/v1/plans",
        json=request_payload(durationMinutes=duration),
    )
    assert response.status_code == 422


def test_quick_rejects_preselected_pois():
    response = client.post(
        "/api/v1/plans",
        json=request_payload(selectedPoiIds=["poi_candidate_1"]),
    )
    assert response.status_code == 422


def test_unknown_fields_are_rejected_in_preferences_and_candidates():
    payload = request_payload()
    payload["preferences"]["unexpected"] = "not allowed"
    response = client.post("/api/v1/plans", json=payload)
    assert response.status_code == 422

    payload = request_payload()
    payload["candidates"][0]["address"] = "not sent to model"
    response = client.post("/api/v1/plans", json=payload)
    assert response.status_code == 422


def test_origin_and_coordinates_are_rejected_at_the_contract_boundary():
    payload = request_payload(origin={"longitude": 113.9, "latitude": 22.7})
    response = client.post("/api/v1/plans", json=payload)
    assert response.status_code == 422

    payload = request_payload()
    payload["candidates"][0]["longitude"] = 113.9
    response = client.post("/api/v1/plans", json=payload)
    assert response.status_code == 422
