"""LLM currency fallback for crawled sites: fired only when neither the
source config nor any page's own schema.org markup states a currency."""
import json
from unittest.mock import AsyncMock, MagicMock, patch

import pytest

from app.services.catalog import crawl


def _llm_returning(payload):
    response = MagicMock()
    response.choices = [MagicMock()]
    response.choices[0].message.content = json.dumps(payload)
    response.usage = MagicMock(prompt_tokens=10, completion_tokens=5, total_tokens=15)
    return response


PAGES = [{"url": "https://shop.example.com.au/", "text": "Free shipping across Australia. Prices in AUD."}]


@pytest.mark.asyncio
async def test_infers_a_confident_currency():
    with patch.object(crawl, "openai_client") as client:
        client.chat.completions.create.return_value = _llm_returning({"currency": "aud"})
        result = await crawl._infer_currency(PAGES, "org_x")

    assert result == "AUD"


@pytest.mark.asyncio
async def test_null_verdict_is_passed_through_as_none():
    with patch.object(crawl, "openai_client") as client:
        client.chat.completions.create.return_value = _llm_returning({"currency": None})
        result = await crawl._infer_currency(PAGES, "org_x")

    assert result is None


@pytest.mark.asyncio
async def test_malformed_currency_value_is_rejected_not_trusted():
    with patch.object(crawl, "openai_client") as client:
        client.chat.completions.create.return_value = _llm_returning({"currency": "not-a-code"})
        result = await crawl._infer_currency(PAGES, "org_x")

    assert result is None


@pytest.mark.asyncio
async def test_llm_failure_returns_none_not_an_exception():
    with patch.object(crawl, "openai_client") as client:
        client.chat.completions.create.side_effect = RuntimeError("timeout")
        result = await crawl._infer_currency(PAGES, "org_x")

    assert result is None


@pytest.mark.asyncio
async def test_no_page_text_short_circuits_without_calling_the_llm():
    with patch.object(crawl, "openai_client") as client:
        result = await crawl._infer_currency([{"url": "https://shop.example.com/", "text": ""}], "org_x")

    assert result is None
    client.chat.completions.create.assert_not_called()


@pytest.mark.asyncio
async def test_fetch_products_uses_the_inferred_currency_when_nothing_else_states_one():
    page = {"url": "https://shop.example.com.au/", "html": "<html></html>", "text": "Prices in AUD."}
    product = {
        "name": "Bucket Hat",
        "product_url": "https://shop.example.com.au/products/bucket-hat",
        "raw": {"jsonld": {}},
    }

    with patch.object(crawl, "assert_safe_url"), \
         patch.object(crawl, "extract_text_from_url", AsyncMock(return_value={"pages": [page]})), \
         patch.object(crawl, "extract_page_products", return_value=[product]), \
         patch.object(crawl, "_infer_currency", AsyncMock(return_value="AUD")) as infer:
        products = await crawl.fetch_products({"url": "https://shop.example.com.au/"}, "shop.example.com.au", "org_x")

    infer.assert_awaited_once()
    assert products[0]["currency"] == "AUD"


@pytest.mark.asyncio
async def test_fetch_products_skips_inference_when_markup_already_states_a_currency():
    page = {"url": "https://shop.example.com/", "html": "<html></html>", "text": "..."}
    product = {
        "name": "Bucket Hat",
        "product_url": "https://shop.example.com/products/bucket-hat",
        "raw": {"jsonld": {"offers": {"price": "10.00", "priceCurrency": "GBP"}}},
    }

    with patch.object(crawl, "assert_safe_url"), \
         patch.object(crawl, "extract_text_from_url", AsyncMock(return_value={"pages": [page]})), \
         patch.object(crawl, "extract_page_products", return_value=[product]), \
         patch.object(crawl, "_infer_currency", AsyncMock(return_value="AUD")) as infer:
        products = await crawl.fetch_products({"url": "https://shop.example.com/"}, "shop.example.com", "org_x")

    infer.assert_not_awaited()
    assert products[0]["currency"] == "GBP"


@pytest.mark.asyncio
async def test_fetch_products_skips_inference_when_source_config_already_has_one():
    page = {"url": "https://shop.example.com/", "html": "<html></html>", "text": "..."}
    product = {
        "name": "Bucket Hat",
        "product_url": "https://shop.example.com/products/bucket-hat",
        "raw": {"jsonld": {}},
    }

    with patch.object(crawl, "assert_safe_url"), \
         patch.object(crawl, "extract_text_from_url", AsyncMock(return_value={"pages": [page]})), \
         patch.object(crawl, "extract_page_products", return_value=[product]), \
         patch.object(crawl, "_infer_currency", AsyncMock(return_value="AUD")) as infer:
        products = await crawl.fetch_products(
            {"url": "https://shop.example.com/", "currency": "USD"}, "shop.example.com", "org_x")

    infer.assert_not_awaited()
    assert products[0]["currency"] == "USD"
