"""Tests for X-Wines dataset API endpoints."""

import random

import pytest
from httpx import AsyncClient

from winebox.models import XWinesMetadata, XWinesWine


@pytest.mark.asyncio
async def test_xwines_search_empty(client: AsyncClient) -> None:
    """Test search with a term that matches no X-Wines data."""
    # Use a unique nonsense term to avoid false matches from parallel tests
    response = await client.get("/api/xwines/search?q=zzzyyyxxx_nonexistent_42")
    assert response.status_code == 200
    data = response.json()
    assert data["results"] == []
    assert data["total"] == 0


@pytest.mark.asyncio
async def test_xwines_search_query_too_short(client: AsyncClient) -> None:
    """Test search with query less than 2 characters."""
    response = await client.get("/api/xwines/search?q=a")
    assert response.status_code == 422  # Validation error


@pytest.mark.asyncio
async def test_xwines_search_with_data(client: AsyncClient, init_test_db) -> None:
    """Test search with X-Wines data in database."""
    uid = random.randint(100000, 999999)
    # Add test wines to database using Beanie
    test_wines = [
        XWinesWine(
            xwines_id=uid,
            name="Chateau Margaux",
            wine_type="Red",
            winery_name="Chateau Margaux",
            country="France",
            country_code="FR",
            region_name="Bordeaux",
            abv=13.5,
            avg_rating=4.5,
            rating_count=1000,
        ),
        XWinesWine(
            xwines_id=uid + 1,
            name="Opus One",
            wine_type="Red",
            winery_name="Opus One Winery",
            country="United States",
            country_code="US",
            region_name="Napa Valley",
            abv=14.0,
            avg_rating=4.7,
            rating_count=500,
        ),
        XWinesWine(
            xwines_id=uid + 2,
            name="Merlot Reserve",
            wine_type="Red",
            winery_name="Test Winery",
            country="Italy",
            country_code="IT",
            region_name="Tuscany",
            abv=13.0,
            avg_rating=3.8,
            rating_count=200,
        ),
    ]
    for wine in test_wines:
        await wine.insert()

    # Search for "Chateau"
    response = await client.get("/api/xwines/search?q=Chateau")
    assert response.status_code == 200
    data = response.json()
    assert data["total"] >= 1
    assert len(data["results"]) >= 1
    assert data["results"][0]["name"] == "Chateau Margaux"


@pytest.mark.asyncio
async def test_xwines_search_by_winery(client: AsyncClient, init_test_db) -> None:
    """Test search matches winery name."""
    uid = random.randint(100000, 999999)
    wine = XWinesWine(
        xwines_id=uid,
        name="Reserve Red",
        wine_type="Red",
        winery_name="Silver Oak Cellars",
        country="United States",
        country_code="US",
        avg_rating=4.2,
        rating_count=300,
    )
    await wine.insert()

    # Search for winery name
    response = await client.get("/api/xwines/search?q=Silver%20Oak")
    assert response.status_code == 200
    data = response.json()
    assert data["total"] >= 1
    assert data["results"][0]["winery"] == "Silver Oak Cellars"


@pytest.mark.asyncio
async def test_xwines_search_with_filters(client: AsyncClient, init_test_db) -> None:
    """Test search with wine_type and country filters."""
    uid = random.randint(100000, 999999)
    wines = [
        XWinesWine(
            xwines_id=uid,
            name="Test Red Wine",
            wine_type="Red",
            country="France",
            country_code="FR",
            avg_rating=4.0,
            rating_count=100,
        ),
        XWinesWine(
            xwines_id=uid + 1,
            name="Test White Wine",
            wine_type="White",
            country="France",
            country_code="FR",
            avg_rating=4.0,
            rating_count=100,
        ),
    ]
    for wine in wines:
        await wine.insert()

    # Search with wine_type filter
    response = await client.get("/api/xwines/search?q=Test&wine_type=Red")
    assert response.status_code == 200
    data = response.json()
    assert data["total"] >= 1
    # All results should be Red when filtered
    for result in data["results"]:
        assert result["wine_type"] == "Red"


@pytest.mark.asyncio
async def test_xwines_get_wine_detail(client: AsyncClient, init_test_db) -> None:
    """Test getting full wine details."""
    uid = random.randint(100000, 999999)
    wine = XWinesWine(
        xwines_id=uid,
        name="Test Wine",
        wine_type="Red",
        elaborate="100%",
        grapes='["Cabernet Sauvignon"]',
        harmonize="Beef, Lamb",
        abv=14.5,
        body="Full-bodied",
        acidity="Medium",
        country="France",
        country_code="FR",
        region_name="Bordeaux",
        winery_name="Test Winery",
        avg_rating=4.3,
        rating_count=250,
    )
    await wine.insert()

    response = await client.get(f"/api/xwines/wines/{uid}")
    assert response.status_code == 200
    data = response.json()
    assert data["id"] == uid
    assert data["name"] == "Test Wine"
    assert data["wine_type"] == "Red"
    assert data["body"] == "Full-bodied"
    assert data["harmonize"] == "Beef, Lamb"


@pytest.mark.asyncio
async def test_xwines_get_wine_not_found(client: AsyncClient) -> None:
    """Test getting a non-existent wine."""
    response = await client.get("/api/xwines/wines/99999")
    assert response.status_code == 404


@pytest.mark.asyncio
async def test_xwines_stats_empty(client: AsyncClient) -> None:
    """Test stats endpoint returns expected structure."""
    response = await client.get("/api/xwines/stats")
    assert response.status_code == 200
    data = response.json()
    assert "wine_count" in data
    assert "rating_count" in data
    assert data["source"] == "https://github.com/rogerioxavier/X-Wines"


@pytest.mark.asyncio
async def test_xwines_stats_with_data(client: AsyncClient, init_test_db) -> None:
    """Test stats endpoint with data."""
    uid = random.randint(100000, 999999)
    # Add wines
    for i in range(5):
        wine = XWinesWine(
            xwines_id=uid + i,
            name=f"Wine {i}",
            wine_type="Red",
            avg_rating=4.0,
            rating_count=100,
        )
        await wine.insert()

    # Add metadata
    metadata = [
        XWinesMetadata(key="version", value="test"),
        XWinesMetadata(key="rating_count", value="500"),
        XWinesMetadata(key="import_date", value="2024-01-15T10:30:00"),
    ]
    for m in metadata:
        await m.insert()

    response = await client.get("/api/xwines/stats")
    assert response.status_code == 200
    data = response.json()
    assert data["wine_count"] >= 5
    assert data["rating_count"] >= 500
    assert data["version"] == "test"


@pytest.mark.asyncio
async def test_xwines_types_endpoint(client: AsyncClient, init_test_db) -> None:
    """Test listing wine types."""
    uid = random.randint(100000, 999999)
    wines = [
        XWinesWine(xwines_id=uid, name="Red Wine", wine_type="Red"),
        XWinesWine(xwines_id=uid + 1, name="White Wine", wine_type="White"),
        XWinesWine(xwines_id=uid + 2, name="Another Red", wine_type="Red"),
        XWinesWine(xwines_id=uid + 3, name="Rosé Wine", wine_type="Rosé"),
    ]
    for wine in wines:
        await wine.insert()

    # Invalidate cache and force refresh so freshly inserted data is picked up
    from winebox.routers.xwines import invalidate_filter_cache, _refresh_filter_cache
    invalidate_filter_cache()
    await _refresh_filter_cache()

    response = await client.get("/api/xwines/types")
    assert response.status_code == 200
    types = response.json()
    assert len(types) >= 3  # Red, White, Rosé (deduplicated), possibly more from other tests
    assert "Red" in types
    assert "White" in types
    assert "Rosé" in types


@pytest.mark.asyncio
async def test_xwines_countries_endpoint(client: AsyncClient, init_test_db) -> None:
    """Test listing countries with wine counts."""
    uid = random.randint(100000, 999999)
    wines = [
        XWinesWine(xwines_id=uid, name="Wine 1", wine_type="Red", country="France", country_code="FR"),
        XWinesWine(xwines_id=uid + 1, name="Wine 2", wine_type="Red", country="France", country_code="FR"),
        XWinesWine(xwines_id=uid + 2, name="Wine 3", wine_type="White", country="Italy", country_code="IT"),
    ]
    for wine in wines:
        await wine.insert()

    # Invalidate cache and force refresh so freshly inserted data is picked up
    from winebox.routers.xwines import invalidate_filter_cache, _refresh_filter_cache
    invalidate_filter_cache()
    await _refresh_filter_cache()

    response = await client.get("/api/xwines/countries")
    assert response.status_code == 200
    countries = response.json()
    assert len(countries) >= 2

    # France and Italy should be present
    france = next((c for c in countries if c["code"] == "FR"), None)
    assert france is not None
    assert france["count"] >= 2

    italy = next((c for c in countries if c["code"] == "IT"), None)
    assert italy is not None
    assert italy["count"] >= 1


@pytest.mark.asyncio
async def test_xwines_search_response_has_facets_field(
    client: AsyncClient, init_test_db
) -> None:
    """Test that search response includes the facets key (null when Atlas Search unavailable)."""
    uid = random.randint(100000, 999999)
    wine = XWinesWine(
        xwines_id=uid,
        name="Facet Test Wine",
        wine_type="Red",
        country="France",
        country_code="FR",
        avg_rating=4.0,
        rating_count=100,
    )
    await wine.insert()

    response = await client.get("/api/xwines/search?q=Facet")
    assert response.status_code == 200
    data = response.json()
    assert "facets" in data
    # In test environment (no Atlas Search), facets should be null
    assert data["facets"] is None
    assert data["total"] >= 1
    assert len(data["results"]) >= 1


@pytest.mark.asyncio
async def test_xwines_search_ordering(client: AsyncClient, init_test_db) -> None:
    """Test search results are ordered by popularity then rating."""
    uid = random.randint(100000, 999999)
    # Use unique names to avoid interference from other tests
    wines = [
        XWinesWine(
            xwines_id=uid,
            name=f"Popular Vino {uid}",
            wine_type="Red",
            avg_rating=4.0,
            rating_count=1000,
        ),
        XWinesWine(
            xwines_id=uid + 1,
            name=f"Quality Vino {uid}",
            wine_type="Red",
            avg_rating=4.9,
            rating_count=100,
        ),
        XWinesWine(
            xwines_id=uid + 2,
            name=f"Unknown Vino {uid}",
            wine_type="Red",
            avg_rating=3.5,
            rating_count=10,
        ),
    ]
    for wine in wines:
        await wine.insert()

    response = await client.get(f"/api/xwines/search?q=Vino%20{uid}")
    assert response.status_code == 200
    data = response.json()
    results = data["results"]

    # Find our specific wines in the results
    our_names = [f"Popular Vino {uid}", f"Quality Vino {uid}", f"Unknown Vino {uid}"]
    our_results = [r for r in results if r["name"] in our_names]
    assert len(our_results) == 3

    # Popular wine should be first (highest rating_count)
    assert our_results[0]["name"] == f"Popular Vino {uid}"
    # Quality wine second (high rating, lower count)
    assert our_results[1]["name"] == f"Quality Vino {uid}"
    # Unknown wine last
    assert our_results[2]["name"] == f"Unknown Vino {uid}"


@pytest.mark.asyncio
async def test_xwines_search_pagination(client: AsyncClient, init_test_db) -> None:
    """Test search pagination with skip and limit parameters."""
    uid = random.randint(100000, 999999)
    # Use unique prefix to avoid interference from other tests
    prefix = f"PagWine{uid}"
    # Create 10 wines
    wines = [
        XWinesWine(
            xwines_id=uid + i,
            name=f"{prefix} {i}",
            wine_type="Red",
            avg_rating=4.0,
            rating_count=1000 - i * 10,  # Ensure ordering
        )
        for i in range(10)
    ]
    for wine in wines:
        await wine.insert()

    # Test first page (limit=3, skip=0)
    response = await client.get(f"/api/xwines/search?q={prefix}&limit=3&skip=0")
    assert response.status_code == 200
    data = response.json()
    assert data["total"] >= 10
    assert data["skip"] == 0
    assert data["limit"] == 3
    assert len(data["results"]) == 3
    assert data["results"][0]["name"] == f"{prefix} 0"

    # Test second page (limit=3, skip=3)
    response = await client.get(f"/api/xwines/search?q={prefix}&limit=3&skip=3")
    assert response.status_code == 200
    data = response.json()
    assert data["total"] >= 10
    assert data["skip"] == 3
    assert data["limit"] == 3
    assert len(data["results"]) == 3
    assert data["results"][0]["name"] == f"{prefix} 3"


@pytest.mark.asyncio
async def test_xwines_search_skip_validation(client: AsyncClient) -> None:
    """Test that negative skip values are rejected."""
    response = await client.get("/api/xwines/search?q=test&skip=-1")
    assert response.status_code == 422  # Validation error


@pytest.mark.asyncio
async def test_xwines_export_json(client: AsyncClient, init_test_db) -> None:
    """Test X-Wines export in JSON format."""
    uid = random.randint(100000, 999999)
    wines = [
        XWinesWine(
            xwines_id=uid,
            name="Export Test Wine",
            wine_type="Red",
            winery_name="Test Winery",
            country="France",
            country_code="FR",
            avg_rating=4.5,
            rating_count=100,
        ),
    ]
    for wine in wines:
        await wine.insert()

    response = await client.get("/api/xwines/export?q=Export&format=json")
    assert response.status_code == 200
    assert response.headers["content-type"] == "application/json"
    assert "attachment" in response.headers.get("content-disposition", "")

    data = response.json()
    assert "xwines" in data
    assert "export_info" in data
    assert len(data["xwines"]) >= 1
    # Check our wine is in the results
    names = [w["name"] for w in data["xwines"]]
    assert "Export Test Wine" in names


@pytest.mark.asyncio
async def test_xwines_export_csv(client: AsyncClient, init_test_db) -> None:
    """Test X-Wines export in CSV format."""
    uid = random.randint(100000, 999999)
    wine = XWinesWine(
        xwines_id=uid,
        name="CSV Export Wine",
        wine_type="White",
        winery_name="CSV Winery",
        country="Italy",
        country_code="IT",
        avg_rating=4.2,
        rating_count=50,
    )
    await wine.insert()

    response = await client.get("/api/xwines/export?q=CSV&format=csv")
    assert response.status_code == 200
    assert "text/csv" in response.headers["content-type"]
    assert "attachment" in response.headers.get("content-disposition", "")

    content = response.content.decode("utf-8")
    assert "id,name,winery" in content  # Check header
    assert "CSV Export Wine" in content


@pytest.mark.asyncio
async def test_xwines_export_xlsx(client: AsyncClient, init_test_db) -> None:
    """Test X-Wines export in XLSX format."""
    uid = random.randint(100000, 999999)
    wine = XWinesWine(
        xwines_id=uid,
        name="Excel Export Wine",
        wine_type="Red",
        winery_name="Excel Winery",
        country="Spain",
        country_code="ES",
        avg_rating=4.0,
        rating_count=75,
    )
    await wine.insert()

    response = await client.get("/api/xwines/export?q=Excel&format=xlsx")
    assert response.status_code == 200
    assert (
        response.headers["content-type"]
        == "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    )
    assert "attachment" in response.headers.get("content-disposition", "")
    # Check it's a valid XLSX (starts with ZIP signature)
    assert response.content[:4] == b"PK\x03\x04"


@pytest.mark.asyncio
async def test_xwines_export_yaml(client: AsyncClient, init_test_db) -> None:
    """Test X-Wines export in YAML format."""
    uid = random.randint(100000, 999999)
    wine = XWinesWine(
        xwines_id=uid,
        name="YAML Export Wine",
        wine_type="Rosé",
        winery_name="YAML Winery",
        country="Portugal",
        country_code="PT",
        avg_rating=3.8,
        rating_count=30,
    )
    await wine.insert()

    response = await client.get("/api/xwines/export?q=YAML&format=yaml")
    assert response.status_code == 200
    assert response.headers["content-type"] == "application/x-yaml"
    assert "attachment" in response.headers.get("content-disposition", "")

    content = response.content.decode("utf-8")
    assert "xwines:" in content
    assert "YAML Export Wine" in content
    assert "export_info:" in content


@pytest.mark.asyncio
async def test_xwines_export_with_filters(client: AsyncClient, init_test_db) -> None:
    """Test X-Wines export with wine_type and country filters."""
    uid = random.randint(100000, 999999)
    wines = [
        XWinesWine(
            xwines_id=uid,
            name="Filter Test Red",
            wine_type="Red",
            country="France",
            country_code="FR",
            avg_rating=4.0,
            rating_count=100,
        ),
        XWinesWine(
            xwines_id=uid + 1,
            name="Filter Test White",
            wine_type="White",
            country="France",
            country_code="FR",
            avg_rating=4.0,
            rating_count=100,
        ),
    ]
    for wine in wines:
        await wine.insert()

    # Export only Red wines
    response = await client.get("/api/xwines/export?q=Filter&format=json&wine_type=Red")
    assert response.status_code == 200
    data = response.json()
    assert len(data["xwines"]) >= 1
    # All results should be Red when filtered
    for w in data["xwines"]:
        assert w["wine_type"] == "Red"
    assert data["export_info"]["filters_applied"]["wine_type"] == "Red"


@pytest.mark.asyncio
async def test_xwines_search_prioritizes_exact_match(client: AsyncClient, init_test_db) -> None:
    """Test that exact phrase matches are prioritized over partial/token matches.

    When searching for "Chateau Madelaine", the exact match should appear first
    even if other "Chateau" wines have higher popularity (rating_count).
    """
    uid = random.randint(100000, 999999)
    wines = [
        # High popularity but only partial match (just "Chateau")
        XWinesWine(
            xwines_id=uid,
            name="Chateau Margaux",
            wine_type="Red",
            winery_name="Chateau Margaux",
            country="France",
            country_code="FR",
            avg_rating=4.8,
            rating_count=10000,  # Very popular
        ),
        # Lower popularity but exact phrase match
        XWinesWine(
            xwines_id=uid + 1,
            name="Chateau Madelaine",
            wine_type="Red",
            winery_name="Chateau Madelaine",
            country="France",
            country_code="FR",
            avg_rating=4.2,
            rating_count=100,  # Less popular
        ),
        # Another partial match with high popularity
        XWinesWine(
            xwines_id=uid + 2,
            name="Chateau Lafite Rothschild",
            wine_type="Red",
            winery_name="Chateau Lafite Rothschild",
            country="France",
            country_code="FR",
            avg_rating=4.9,
            rating_count=8000,  # Very popular
        ),
    ]
    for wine in wines:
        await wine.insert()

    # Search for "Chateau Madelaine" - exact match should be first
    response = await client.get("/api/xwines/search?q=Chateau%20Madelaine")
    assert response.status_code == 200
    data = response.json()

    # The exact match "Chateau Madelaine" should be first, not the more popular wines
    assert len(data["results"]) >= 1
    assert data["results"][0]["name"] == "Chateau Madelaine"


@pytest.mark.asyncio
async def test_xwines_search_phrase_vs_token(client: AsyncClient, init_test_db) -> None:
    """Test that full phrase matches rank higher than token matches.

    When searching for "Silver Oak", a wine with "Silver Oak" as a phrase
    should rank higher than a wine with just "Silver" or just "Oak".
    """
    uid = random.randint(100000, 999999)
    wines = [
        # Contains "Oak" but not "Silver Oak" phrase
        XWinesWine(
            xwines_id=uid,
            name="Golden Oak Reserve",
            wine_type="Red",
            winery_name="Oak Valley Winery",
            country="United States",
            country_code="US",
            avg_rating=4.5,
            rating_count=5000,  # Popular
        ),
        # Exact phrase "Silver Oak"
        XWinesWine(
            xwines_id=uid + 1,
            name="Silver Oak Cabernet",
            wine_type="Red",
            winery_name="Silver Oak Cellars",
            country="United States",
            country_code="US",
            avg_rating=4.3,
            rating_count=200,  # Less popular
        ),
        # Contains "Silver" but not "Silver Oak" phrase
        XWinesWine(
            xwines_id=uid + 2,
            name="Silver Creek Merlot",
            wine_type="Red",
            winery_name="Silver Creek Winery",
            country="United States",
            country_code="US",
            avg_rating=4.4,
            rating_count=3000,  # Popular
        ),
    ]
    for wine in wines:
        await wine.insert()

    # Search for "Silver Oak" - phrase match should be first
    response = await client.get("/api/xwines/search?q=Silver%20Oak")
    assert response.status_code == 200
    data = response.json()

    # The phrase match "Silver Oak Cabernet" should be first
    assert len(data["results"]) >= 1
    assert data["results"][0]["name"] == "Silver Oak Cabernet"


@pytest.mark.asyncio
async def test_xwines_search_start_of_name_priority(client: AsyncClient, init_test_db) -> None:
    """Test that matches at the start of name rank higher than matches elsewhere."""
    uid = random.randint(900000, 999999)
    # Use a unique term to avoid contamination from other test data
    term = f"Zinfandel{uid}"
    wines = [
        # Term at the end of name
        XWinesWine(
            xwines_id=uid,
            name=f"Premium Reserve {term}",
            wine_type="Red",
            country="France",
            country_code="FR",
            avg_rating=4.5,
            rating_count=5000,  # Popular
        ),
        # Term at the start of name
        XWinesWine(
            xwines_id=uid + 1,
            name=f"{term} Classic Reserve",
            wine_type="Red",
            country="France",
            country_code="FR",
            avg_rating=4.0,
            rating_count=100,  # Less popular
        ),
    ]
    for wine in wines:
        await wine.insert()

    # Search for the term - start-of-name match should be first
    response = await client.get(f"/api/xwines/search?q={term}")
    assert response.status_code == 200
    data = response.json()

    # The start-of-name match should be first
    assert len(data["results"]) >= 1
    assert data["results"][0]["name"] == f"{term} Classic Reserve"
