"""Tests for memories convenience layer — content_type inference, retrieve body building, batch ops,
and all three content ingestion wire formats (original_content, original_content_b64, file_paths)."""

import base64
import json

import httpx
import pytest

from goodmem.api.memories import _build_retrieve_body, _infer_content_type
from tests.conftest import MEMORY_RESPONSE


# --- _infer_content_type ---

def test_infer_content_type_pdf():
    assert _infer_content_type("doc.pdf") == "application/pdf"


def test_infer_content_type_txt():
    assert _infer_content_type("notes.txt") == "text/plain"


def test_infer_content_type_unknown():
    assert _infer_content_type("file.xyz123") == "application/octet-stream"


def test_infer_content_type_html():
    assert _infer_content_type("page.html") == "text/html"


# --- Text content_type auto-inference ---

def test_text_content_auto_infers_content_type(mock_client_factory):
    captured = {}

    def handler(request: httpx.Request) -> httpx.Response:
        captured["body"] = json.loads(request.content)
        return httpx.Response(200, json=MEMORY_RESPONSE)

    client = mock_client_factory(handler)
    client.memories.create(
        space_id="space-123",
        original_content="Hello world",
    )

    assert captured["body"]["contentType"] == "text/plain"


def test_explicit_content_type_wins(mock_client_factory):
    captured = {}

    def handler(request: httpx.Request) -> httpx.Response:
        captured["body"] = json.loads(request.content)
        return httpx.Response(200, json=MEMORY_RESPONSE)

    client = mock_client_factory(handler)
    client.memories.create(
        space_id="space-123",
        original_content="Hello world",
        content_type="text/markdown",
    )

    assert captured["body"]["contentType"] == "text/markdown"


# --- Three content ingestion wire formats ---

def test_original_content_sends_json_body(mock_client_factory):
    """Path 1: original_content sends inline text in a JSON body."""
    captured = {}

    def handler(request: httpx.Request) -> httpx.Response:
        captured["content_type"] = request.headers.get("content-type", "")
        captured["body"] = json.loads(request.content)
        return httpx.Response(200, json=MEMORY_RESPONSE)

    client = mock_client_factory(handler)
    client.memories.create(
        space_id="space-123",
        original_content="Hello world",
    )

    assert "application/json" in captured["content_type"]
    assert captured["body"]["originalContent"] == "Hello world"
    assert captured["body"]["contentType"] == "text/plain"
    assert "originalContentB64" not in captured["body"]


def test_original_content_b64_sends_json_body(mock_client_factory):
    """Path 2: original_content_b64 sends base64-encoded binary in a JSON body."""
    captured = {}

    def handler(request: httpx.Request) -> httpx.Response:
        captured["content_type"] = request.headers.get("content-type", "")
        captured["body"] = json.loads(request.content)
        return httpx.Response(200, json=MEMORY_RESPONSE)

    client = mock_client_factory(handler)
    raw_bytes = b"%PDF-1.4 fake pdf content"
    b64_content = base64.standard_b64encode(raw_bytes).decode("ascii")
    client.memories.create(
        space_id="space-123",
        content_type="application/pdf",
        original_content_b64=b64_content,
    )

    assert "application/json" in captured["content_type"]
    assert captured["body"]["originalContentB64"] == b64_content
    assert captured["body"]["contentType"] == "application/pdf"
    assert "originalContent" not in captured["body"]


def test_file_path_sends_multipart(mock_client_factory, tmp_path):
    """Path 3: file_path sends a multipart form upload with 'file' and 'request' parts."""
    captured = {}

    def handler(request: httpx.Request) -> httpx.Response:
        captured["content_type"] = request.headers.get("content-type", "")
        captured["raw"] = request.content
        return httpx.Response(200, json=MEMORY_RESPONSE)

    client = mock_client_factory(handler)

    # Create a temp file to upload
    pdf_file = tmp_path / "test.pdf"
    pdf_file.write_bytes(b"%PDF-1.4 fake pdf content")

    client.memories.create(
        space_id="space-123",
        file_path=str(pdf_file),
    )

    assert "multipart/form-data" in captured["content_type"]
    # The raw body should contain the file part and the request JSON part
    raw = captured["raw"]
    assert b"test.pdf" in raw
    assert b"%PDF-1.4 fake pdf content" in raw
    # The request part contains the JSON metadata
    assert b"spaceId" in raw


def test_file_path_infers_content_type(mock_client_factory, tmp_path):
    """file_path auto-infers content_type from file extension."""
    captured = {}

    def handler(request: httpx.Request) -> httpx.Response:
        captured["raw"] = request.content
        return httpx.Response(200, json=MEMORY_RESPONSE)

    client = mock_client_factory(handler)

    pdf_file = tmp_path / "document.pdf"
    pdf_file.write_bytes(b"%PDF-1.4 fake")

    client.memories.create(
        space_id="space-123",
        file_path=str(pdf_file),
    )

    # The request JSON part should contain the inferred content type
    raw = captured["raw"].decode("utf-8", errors="replace")
    assert "application/pdf" in raw


def test_file_path_not_found_raises():
    """file_path raises FileNotFoundError for missing files."""
    from goodmem.api.memories import MemoriesAPI
    import httpx as _httpx

    api = MemoriesAPI(
        _httpx.Client(base_url="http://test"),
        {"x-api-key": "gm_test"},
    )
    with pytest.raises(FileNotFoundError):
        api.create(space_id="s1", file_path="/nonexistent/file.pdf")


# --- _build_retrieve_body ---

def test_space_ids_converted_to_space_keys():
    body = _build_retrieve_body("query", {"space_ids": ["s1", "s2"]})
    # After model_dump(by_alias=True), spaceKeys is the serialized key
    assert "spaceKeys" in body
    keys = body["spaceKeys"]
    assert len(keys) == 2
    assert keys[0]["spaceId"] == "s1"
    assert keys[1]["spaceId"] == "s2"


def test_space_ids_string_converted_to_list():
    body = _build_retrieve_body("query", {"space_ids": "s1"})
    assert body["spaceKeys"][0]["spaceId"] == "s1"


def test_space_keys_passed_through():
    """space_keys with full SpaceKey dicts are passed through to the body."""
    body = _build_retrieve_body("query", {
        "space_keys": [
            {
                "space_id": "s1",
                "embedder_weights": [{"embedder_id": "emb-1", "weight": 1.5}],
                "filter": "lower(CAST(val('$.city') AS TEXT)) = 'manila'",
            },
            {"space_id": "s2"},
        ],
    })
    assert "spaceKeys" in body
    keys = body["spaceKeys"]
    assert len(keys) == 2
    assert keys[0]["spaceId"] == "s1"
    assert keys[0]["embedderWeights"] == [{"embedderId": "emb-1", "weight": 1.5}]
    assert keys[0]["filter"] == "lower(CAST(val('$.city') AS TEXT)) = 'manila'"
    assert keys[1]["spaceId"] == "s2"
    assert "embedderWeights" not in keys[1]


def test_space_ids_and_space_keys_conflict():
    """space_ids and space_keys are mutually exclusive."""
    with pytest.raises(ValueError, match="Cannot specify both"):
        _build_retrieve_body("query", {
            "space_ids": ["s1"],
            "space_keys": [{"space_id": "s2"}],
        })


def test_post_processor_flattening():
    kwargs = {
        "space_ids": ["s1"],
        "llm_id": "llm-1",
        "reranker_id": "rr-1",
        "relevance_threshold": 0.5,
        "max_results": 10,
    }
    body = _build_retrieve_body("query", kwargs)

    assert "postProcessor" in body
    pp = body["postProcessor"]
    assert pp["name"] == "com.goodmem.retrieval.postprocess.ChatPostProcessorFactory"
    assert pp["config"]["llm_id"] == "llm-1"
    assert pp["config"]["reranker_id"] == "rr-1"
    assert pp["config"]["relevance_threshold"] == 0.5
    assert pp["config"]["max_results"] == 10


def test_post_processor_and_individual_args_conflict():
    kwargs = {
        "space_ids": ["s1"],
        "llm_id": "llm-1",
        "post_processor": {"name": "custom", "config": {}},
    }
    with pytest.raises(ValueError, match="Cannot specify both"):
        _build_retrieve_body("query", kwargs)


def test_explicit_post_processor_passed_through():
    custom_pp = {"name": "custom.Processor", "config": {"key": "value"}}
    kwargs = {"space_ids": ["s1"], "post_processor": custom_pp}
    body = _build_retrieve_body("query", kwargs)
    assert body["postProcessor"] == custom_pp


def test_no_post_processor_when_no_args():
    kwargs = {"space_ids": ["s1"]}
    body = _build_retrieve_body("query", kwargs)
    assert "postProcessor" not in body


# --- batch_get / batch_delete ---

def test_batch_get_wraps_ids(mock_client_factory):
    captured = {}

    def handler(request: httpx.Request) -> httpx.Response:
        captured["body"] = json.loads(request.content)
        captured["url"] = str(request.url)
        return httpx.Response(200, json={"results": []})

    client = mock_client_factory(handler)
    client.memories.batch_get(memory_ids=["id1", "id2"])

    assert captured["body"]["memoryIds"] == ["id1", "id2"]
    assert ":batchGet" in captured["url"]


def test_batch_delete_wraps_ids_raises_validation_error():
    """batch_delete currently passes memory_ids to BatchMemoryDeletionRequest which
    expects 'requests' field — this is a known SDK issue to be fixed."""
    from goodmem._generated.models.batch_memory_deletion_request import BatchMemoryDeletionRequest
    with pytest.raises(Exception):
        # The model expects 'requests', not 'memory_ids'
        BatchMemoryDeletionRequest.model_validate({"memory_ids": ["id1", "id2"]})


# --- batch_create content_type auto-inference ---

def test_batch_create_auto_infers_content_type(mock_client_factory):
    from goodmem.api.memories import MemoryCreationRequest

    captured = {}

    def handler(request: httpx.Request) -> httpx.Response:
        captured["body"] = json.loads(request.content)
        return httpx.Response(200, json={"results": []})

    client = mock_client_factory(handler)
    client.memories.batch_create(requests=[
        MemoryCreationRequest(space_id="s1", original_content="text1"),
        MemoryCreationRequest(space_id="s1", original_content="text2"),
    ])

    for req in captured["body"]["requests"]:
        assert req["contentType"] == "text/plain"
