from pathlib import Path
import sys
from uuid import UUID
from unittest.mock import MagicMock

import pytest

sys.path.insert(0, str(Path(__file__).resolve().parents[1]))

from gaunt.client import Client
from gaunt.enums import ScoringTag


def _build_client() -> tuple[Client, MagicMock]:
    mock_response = MagicMock()
    mock_response.content = b'{"accepted": true}'
    mock_response.json.return_value = {"accepted": True}
    mock_response.raise_for_status.return_value = None

    mock_sync = MagicMock()
    mock_sync.post.return_value = mock_response

    mock_async = MagicMock()

    client = Client(
        api_key="test-api-key",
        base_url="https://analytics.example.com",
        sync_http_client=mock_sync,
        async_http_client=mock_async,
    )
    return client, mock_sync


def test_simple_text_chat_logging_event() -> None:
    client, mock_sync = _build_client()

    response = client.log(
        project_id="proj_123",
        inputs={
            "messages": [
                {"role": "system", "content": "You are helpful."},
                {"role": "user", "content": "Say hello"},
            ]
        },
        outputs={"raw": "Hello!"},
        scoring_tags=[ScoringTag.TOXICITY],
        metadata={"app": "chat-ui"},
    )

    assert response == {"accepted": True}
    mock_sync.post.assert_called_once()
    _, kwargs = mock_sync.post.call_args
    payload = kwargs["json"]

    assert kwargs["json"]["project_id"] == "proj_123"
    assert payload["inputs"]["messages"][1]["content"] == "Say hello"
    assert payload["outputs"]["raw"] == "Hello!"
    assert payload["config"]["scoring_tags"] == ["score:toxicity"]
    assert payload["metadata"] == {"app": "chat-ui"}
    assert isinstance(UUID(payload["trace_id"]), UUID)


def test_rag_logging_event_with_context_and_tag() -> None:
    client, mock_sync = _build_client()

    client.log(
        project_id="proj_rag",
        inputs={
            "messages": [{"role": "user", "content": "What is retrieval augmentation?"}],
            "rag_context": [
                {
                    "content": "RAG combines retrieval and generation.",
                    "source_id": "doc-1",
                    "score": 0.97,
                },
                {
                    "content": "It improves grounded responses.",
                    "source_id": "doc-2",
                    "score": 0.92,
                    "metadata": {"section": "overview"},
                },
            ],
        },
        outputs={"raw": "RAG improves grounding by using retrieved context."},
        scoring_tags=[ScoringTag.RAG_FAITHFULNESS],
    )

    _, kwargs = mock_sync.post.call_args
    payload = kwargs["json"]

    assert payload["project_id"] == "proj_rag"
    assert len(payload["inputs"]["rag_context"]) == 2
    assert payload["inputs"]["rag_context"][0]["source_id"] == "doc-1"
    assert payload["config"]["scoring_tags"] == ["score:rag_faithfulness"]


def test_vision_logging_event_with_image_url() -> None:
    client, mock_sync = _build_client()

    client.log(
        project_id="proj_vision",
        inputs={
            "messages": [{"role": "user", "content": "What is in this image?"}],
            "images": [{"url": "https://example.com/image.jpg", "mime_type": "image/jpeg"}],
        },
        outputs={"raw": "A cat sitting on a couch."},
        scoring_tags=[ScoringTag.VISION_HALLUCINATION],
    )

    _, kwargs = mock_sync.post.call_args
    payload = kwargs["json"]

    assert payload["inputs"]["images"][0]["url"] == "https://example.com/image.jpg"
    assert payload["inputs"]["images"][0]["mime_type"] == "image/jpeg"
    assert payload["config"]["scoring_tags"] == ["score:vision_hallucination"]


def test_structured_output_event_with_json_schema() -> None:
    client, mock_sync = _build_client()

    schema = {
        "type": "object",
        "properties": {"name": {"type": "string"}, "age": {"type": "integer"}},
        "required": ["name", "age"],
    }

    client.log(
        project_id="proj_schema",
        inputs={
            "messages": [{"role": "user", "content": "Return person JSON"}],
            "json_schema": schema,
        },
        outputs={"raw": '{"name":"Ada","age":36}', "parsed": {"name": "Ada", "age": 36}},
        expected={"name": "Ada", "age": 36},
        scoring_tags=[ScoringTag.SCHEMA_VALIDITY],
    )

    _, kwargs = mock_sync.post.call_args
    payload = kwargs["json"]

    assert payload["inputs"]["json_schema"] == schema
    assert payload["outputs"]["parsed"] == {"name": "Ada", "age": 36}
    assert payload["expected"] == {"name": "Ada", "age": 36}
    assert payload["config"]["scoring_tags"] == ["score:schema_validity"]


def test_image_input_rejects_both_url_and_base64() -> None:
    client, _ = _build_client()

    with pytest.raises(ValueError):
        client.log(
            project_id="proj_invalid",
            inputs={"images": [{"url": "https://example.com/x.jpg", "base64": "abc"}]},
            outputs={"raw": "invalid"},
        )


def test_numeric_and_answer_correctness_scoring_tags() -> None:
    client, mock_sync = _build_client()

    client.log(
        project_id="proj_scores",
        inputs={"messages": [{"role": "user", "content": "2 + 2?"}]},
        outputs={"raw": "4"},
        scoring_tags=[ScoringTag.NUMERIC_SCORING, ScoringTag.ANSWER_CORRECTNESS],
    )

    _, kwargs = mock_sync.post.call_args
    payload = kwargs["json"]

    assert payload["config"]["scoring_tags"] == [
        "score:numeric_scoring",
        "score:answer_correctness",
    ]
