"""Tests for the VTON (Virtual Try-On) endpoint."""

import uuid

import pytest
from httpx import AsyncClient

from tests.conftest import _mock_analyzer, _mock_llm

GLOBAL_USER_ID = "00000000-0000-0000-0000-000000000000"


async def _analyze_and_generate_ghost(client: AsyncClient, sample_jpeg: bytes) -> str:
    """Helper: run analyze (auto_save) then generate ghost. Returns garment_id."""
    resp = await client.post(
        "/api/v1/garments/analyze",
        files={"image": ("test.jpg", sample_jpeg, "image/jpeg")},
    )
    assert resp.status_code == 200, resp.text
    garment_id = resp.json()["garments"][0]["id"]

    resp = await client.post(f"/api/v1/garments/{garment_id}/ghost")
    assert resp.status_code == 200, resp.text
    assert resp.json()["ghost_image_url"]
    return garment_id


@pytest.mark.asyncio
async def test_vton_no_face_returns_400(client: AsyncClient, sample_jpeg: bytes):
    """Person image with no detectable face → 400."""
    garment_id = await _analyze_and_generate_ghost(client, sample_jpeg)

    _mock_analyzer.get_embeddings.return_value = []

    resp = await client.post(
        "/api/v1/vton/generate",
        files={"image": ("person.jpg", sample_jpeg, "image/jpeg")},
        data={"garment_ids": [garment_id]},
    )
    assert resp.status_code == 400
    assert "얼굴" in resp.json()["detail"]


@pytest.mark.asyncio
async def test_vton_garment_not_found_returns_404(client: AsyncClient, sample_jpeg: bytes):
    """Unknown garment_id → 404."""
    bogus = str(uuid.uuid4())
    resp = await client.post(
        "/api/v1/vton/generate",
        files={"image": ("person.jpg", sample_jpeg, "image/jpeg")},
        data={"garment_ids": [bogus]},
    )
    assert resp.status_code == 404
    assert "의류" in resp.json()["detail"]


@pytest.mark.asyncio
async def test_vton_garment_without_ghost_returns_400(
    client: AsyncClient, sample_jpeg: bytes
):
    """Garment that has no ghost_image_path yet → 400."""
    resp = await client.post(
        "/api/v1/garments/analyze",
        files={"image": ("test.jpg", sample_jpeg, "image/jpeg")},
    )
    assert resp.status_code == 200
    garment_id = resp.json()["garments"][0]["id"]
    # no /ghost call → ghost_image_path is null

    resp = await client.post(
        "/api/v1/vton/generate",
        files={"image": ("person.jpg", sample_jpeg, "image/jpeg")},
        data={"garment_ids": [garment_id]},
    )
    assert resp.status_code == 400
    assert "고스트" in resp.json()["detail"]


@pytest.mark.asyncio
async def test_vton_happy_path(client: AsyncClient, sample_jpeg: bytes):
    """Single garment with ghost → 200 with result URL + all used."""
    garment_id = await _analyze_and_generate_ghost(client, sample_jpeg)

    resp = await client.post(
        "/api/v1/vton/generate",
        files={"image": ("person.jpg", sample_jpeg, "image/jpeg")},
        data={"garment_ids": [garment_id]},
    )
    assert resp.status_code == 200, resp.text
    data = resp.json()
    assert data["result_image_url"].startswith("/storage/vton/")
    assert data["result_image_path"].startswith("vton/")
    assert data["used_garment_ids"] == [garment_id]
    assert data["skipped_garment_ids"] == []
    assert data["llm_note"] == "Used all garments."


@pytest.mark.asyncio
async def test_vton_llm_note_parses_skip(client: AsyncClient, sample_jpeg: bytes):
    """When the LLM note says "Skipped #2", the second garment moves to skipped."""
    g1 = await _analyze_and_generate_ghost(client, sample_jpeg)
    g2 = await _analyze_and_generate_ghost(client, sample_jpeg)

    _mock_llm.vton_note = "Skipped #2 because the person's legs are not visible in frame."

    resp = await client.post(
        "/api/v1/vton/generate",
        files={"image": ("person.jpg", sample_jpeg, "image/jpeg")},
        data={"garment_ids": [g1, g2]},
    )
    assert resp.status_code == 200, resp.text
    data = resp.json()
    assert data["used_garment_ids"] == [g1]
    assert data["skipped_garment_ids"] == [g2]
    assert "#2" in data["llm_note"]


@pytest.mark.asyncio
async def test_vton_missing_garment_ids_returns_422(
    client: AsyncClient, sample_jpeg: bytes
):
    """Missing garment_ids field → FastAPI validation 422."""
    resp = await client.post(
        "/api/v1/vton/generate",
        files={"image": ("person.jpg", sample_jpeg, "image/jpeg")},
    )
    assert resp.status_code == 422


@pytest.mark.asyncio
async def test_vton_with_image_url_happy_path(
    client: AsyncClient, sample_jpeg: bytes, monkeypatch: pytest.MonkeyPatch
):
    """image_url path: server fetches the URL, then pipeline runs end-to-end."""
    garment_id = await _analyze_and_generate_ghost(client, sample_jpeg)

    async def fake_fetch(url: str, timeout: float = 15.0) -> bytes:
        assert url == "https://example.com/person.jpg"
        return sample_jpeg

    # Patch the reference imported into the router module, not the source.
    monkeypatch.setattr("app.routers.vton.fetch_image_bytes", fake_fetch)

    resp = await client.post(
        "/api/v1/vton/generate",
        data={
            "image_url": "https://example.com/person.jpg",
            "garment_ids": [garment_id],
        },
    )
    assert resp.status_code == 200, resp.text
    data = resp.json()
    assert data["result_image_url"].startswith("/storage/vton/")
    assert data["used_garment_ids"] == [garment_id]


@pytest.mark.asyncio
async def test_vton_both_image_and_url_returns_400(
    client: AsyncClient, sample_jpeg: bytes
):
    """Providing both image file and image_url → 400 (XOR)."""
    garment_id = await _analyze_and_generate_ghost(client, sample_jpeg)

    resp = await client.post(
        "/api/v1/vton/generate",
        files={"image": ("person.jpg", sample_jpeg, "image/jpeg")},
        data={
            "image_url": "https://example.com/person.jpg",
            "garment_ids": [garment_id],
        },
    )
    assert resp.status_code == 400
    assert "image" in resp.json()["detail"]


@pytest.mark.asyncio
async def test_vton_neither_image_nor_url_returns_400(
    client: AsyncClient, sample_jpeg: bytes
):
    """Providing neither image file nor image_url → 400 (XOR)."""
    garment_id = await _analyze_and_generate_ghost(client, sample_jpeg)

    resp = await client.post(
        "/api/v1/vton/generate",
        data={"garment_ids": [garment_id]},
    )
    assert resp.status_code == 400
    assert "image" in resp.json()["detail"]


@pytest.mark.asyncio
async def test_vton_image_url_fetch_failure_returns_400(
    client: AsyncClient, sample_jpeg: bytes, monkeypatch: pytest.MonkeyPatch
):
    """Server-side URL fetch raising ValueError → 400 propagated to client."""
    garment_id = await _analyze_and_generate_ghost(client, sample_jpeg)

    async def failing_fetch(url: str, timeout: float = 15.0) -> bytes:
        raise ValueError("이미지를 가져오지 못했습니다: HTTP 404")

    monkeypatch.setattr("app.routers.vton.fetch_image_bytes", failing_fetch)

    resp = await client.post(
        "/api/v1/vton/generate",
        data={
            "image_url": "https://example.com/missing.jpg",
            "garment_ids": [garment_id],
        },
    )
    assert resp.status_code == 400
    assert "HTTP 404" in resp.json()["detail"]
