"""Tests for CRUD operations (get, list, update, delete) across all resource types.

Complements the existing per-resource test files which focus on create defaults
and convenience-layer logic.  These tests exercise the base CRUDNamespace
methods (get, list, update, delete) and verify correct HTTP method, path, and
response parsing.
"""

import json

import httpx
import pytest

from goodmem._generated.models.llm_update_request import LLMUpdateRequest
from goodmem._generated.models.update_embedder_request import UpdateEmbedderRequest
from goodmem._generated.models.update_reranker_request import UpdateRerankerRequest
from goodmem._generated.models.update_space_request import UpdateSpaceRequest
from tests.conftest import (
    EMBEDDER_RESPONSE,
    LLM_RESPONSE,
    MEMORY_RESPONSE,
    RERANKER_RESPONSE,
    SPACE_RESPONSE,
    _TS,
)


# ---------------------------------------------------------------------------
# Embedder CRUD
# ---------------------------------------------------------------------------

class TestEmbedderCRUD:

    def test_get_embedder(self, mock_client_factory):
        captured = {}

        def handler(request: httpx.Request) -> httpx.Response:
            captured["method"] = request.method
            captured["url"] = str(request.url)
            return httpx.Response(200, json=EMBEDDER_RESPONSE)

        client = mock_client_factory(handler)
        result = client.embedders.get(id="emb-123")

        assert captured["method"] == "GET"
        assert "/v1/embedders/emb-123" in captured["url"]
        assert result.embedder_id == "emb-123"
        assert result.display_name == "Test Embedder"

    def test_list_embedders(self, mock_client_factory):
        captured = {}
        list_response = {"embedders": [EMBEDDER_RESPONSE]}

        def handler(request: httpx.Request) -> httpx.Response:
            captured["method"] = request.method
            captured["url"] = str(request.url)
            return httpx.Response(200, json=list_response)

        client = mock_client_factory(handler)
        items = list(client.embedders.list())

        assert captured["method"] == "GET"
        assert "/v1/embedders" in captured["url"]
        assert len(items) == 1
        assert items[0].embedder_id == "emb-123"

    def test_update_embedder(self, mock_client_factory):
        captured = {}
        updated_response = {**EMBEDDER_RESPONSE, "displayName": "Updated Name"}

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

        client = mock_client_factory(handler)
        result = client.embedders.update(id="emb-123", request=UpdateEmbedderRequest(display_name="Updated Name"))

        assert captured["method"] == "PUT"
        assert "/v1/embedders/emb-123" in captured["url"]
        assert captured["body"]["displayName"] == "Updated Name"
        assert result.display_name == "Updated Name"

    def test_delete_embedder(self, mock_client_factory):
        captured = {}

        def handler(request: httpx.Request) -> httpx.Response:
            captured["method"] = request.method
            captured["url"] = str(request.url)
            return httpx.Response(204)

        client = mock_client_factory(handler)
        client.embedders.delete(id="emb-123")

        assert captured["method"] == "DELETE"
        assert "/v1/embedders/emb-123" in captured["url"]


# ---------------------------------------------------------------------------
# LLM CRUD
# ---------------------------------------------------------------------------

class TestLlmCRUD:

    def test_get_llm(self, mock_client_factory):
        captured = {}

        def handler(request: httpx.Request) -> httpx.Response:
            captured["method"] = request.method
            captured["url"] = str(request.url)
            return httpx.Response(200, json=LLM_RESPONSE)

        client = mock_client_factory(handler)
        result = client.llms.get(id="llm-123")

        assert captured["method"] == "GET"
        assert "/v1/llms/llm-123" in captured["url"]
        assert result.llm_id == "llm-123"

    def test_list_llms(self, mock_client_factory):
        captured = {}
        list_response = {"llms": [LLM_RESPONSE]}

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

        client = mock_client_factory(handler)
        items = list(client.llms.list())

        assert len(items) == 1
        assert items[0].llm_id == "llm-123"

    def test_update_llm(self, mock_client_factory):
        captured = {}
        updated_response = {**LLM_RESPONSE, "displayName": "Updated LLM"}

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

        client = mock_client_factory(handler)
        result = client.llms.update(id="llm-123", request=LLMUpdateRequest(display_name="Updated LLM"))

        assert captured["method"] == "PUT"
        assert "/v1/llms/llm-123" in captured["url"]
        assert result.display_name == "Updated LLM"

    def test_delete_llm(self, mock_client_factory):
        captured = {}

        def handler(request: httpx.Request) -> httpx.Response:
            captured["method"] = request.method
            captured["url"] = str(request.url)
            return httpx.Response(204)

        client = mock_client_factory(handler)
        client.llms.delete(id="llm-123")

        assert captured["method"] == "DELETE"
        assert "/v1/llms/llm-123" in captured["url"]


# ---------------------------------------------------------------------------
# Reranker CRUD
# ---------------------------------------------------------------------------

class TestRerankerCRUD:

    def test_get_reranker(self, mock_client_factory):
        captured = {}

        def handler(request: httpx.Request) -> httpx.Response:
            captured["method"] = request.method
            captured["url"] = str(request.url)
            return httpx.Response(200, json=RERANKER_RESPONSE)

        client = mock_client_factory(handler)
        result = client.rerankers.get(id="rr-123")

        assert captured["method"] == "GET"
        assert "/v1/rerankers/rr-123" in captured["url"]
        assert result.reranker_id == "rr-123"

    def test_list_rerankers(self, mock_client_factory):
        captured = {}
        list_response = {"rerankers": [RERANKER_RESPONSE]}

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

        client = mock_client_factory(handler)
        items = list(client.rerankers.list())

        assert len(items) == 1
        assert items[0].reranker_id == "rr-123"

    def test_update_reranker(self, mock_client_factory):
        captured = {}
        updated_response = {**RERANKER_RESPONSE, "displayName": "Updated Reranker"}

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

        client = mock_client_factory(handler)
        result = client.rerankers.update(id="rr-123", request=UpdateRerankerRequest(display_name="Updated Reranker"))

        assert captured["method"] == "PUT"
        assert "/v1/rerankers/rr-123" in captured["url"]
        assert result.display_name == "Updated Reranker"

    def test_delete_reranker(self, mock_client_factory):
        captured = {}

        def handler(request: httpx.Request) -> httpx.Response:
            captured["method"] = request.method
            captured["url"] = str(request.url)
            return httpx.Response(204)

        client = mock_client_factory(handler)
        client.rerankers.delete(id="rr-123")

        assert captured["method"] == "DELETE"
        assert "/v1/rerankers/rr-123" in captured["url"]


# ---------------------------------------------------------------------------
# Space CRUD
# ---------------------------------------------------------------------------

_SPACE_EMBEDDER = {
    "spaceId": "space-123",
    "embedderId": "emb-123",
    "defaultRetrievalWeight": 1.0,
    "createdAt": _TS,
    "updatedAt": _TS,
    "createdById": "user-1",
    "updatedById": "user-1",
}

class TestSpaceCRUD:

    def test_get_space(self, mock_client_factory):
        captured = {}

        def handler(request: httpx.Request) -> httpx.Response:
            captured["method"] = request.method
            captured["url"] = str(request.url)
            return httpx.Response(200, json=SPACE_RESPONSE)

        client = mock_client_factory(handler)
        result = client.spaces.get(id="space-123")

        assert captured["method"] == "GET"
        assert "/v1/spaces/space-123" in captured["url"]
        assert result.space_id == "space-123"

    def test_list_spaces(self, mock_client_factory):
        captured = {}
        list_response = {"spaces": [SPACE_RESPONSE]}

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

        client = mock_client_factory(handler)
        items = list(client.spaces.list())

        assert len(items) == 1
        assert items[0].space_id == "space-123"

    def test_update_space(self, mock_client_factory):
        captured = {}
        updated_response = {**SPACE_RESPONSE, "name": "Updated Space"}

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

        client = mock_client_factory(handler)
        result = client.spaces.update(id="space-123", request=UpdateSpaceRequest(name="Updated Space"))

        assert captured["method"] == "PUT"
        assert "/v1/spaces/space-123" in captured["url"]
        assert result.name == "Updated Space"

    def test_delete_space(self, mock_client_factory):
        captured = {}

        def handler(request: httpx.Request) -> httpx.Response:
            captured["method"] = request.method
            captured["url"] = str(request.url)
            return httpx.Response(204)

        client = mock_client_factory(handler)
        client.spaces.delete(id="space-123")

        assert captured["method"] == "DELETE"
        assert "/v1/spaces/space-123" in captured["url"]

    def test_create_space_dual_embedders_with_weights(self, mock_client_factory):
        """Verify space creation with two embedders and retrieval weights."""
        captured = {}
        dual_embedder_response = {
            **SPACE_RESPONSE,
            "spaceEmbedders": [
                {**_SPACE_EMBEDDER, "embedderId": "emb-large", "defaultRetrievalWeight": 0.7},
                {**_SPACE_EMBEDDER, "embedderId": "emb-small", "defaultRetrievalWeight": 0.3},
            ],
        }

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

        client = mock_client_factory(handler)
        result = client.spaces.create(
            name="Weighted Space",
            space_embedders=[
                {"embedder_id": "emb-large", "default_retrieval_weight": 0.7},
                {"embedder_id": "emb-small", "default_retrieval_weight": 0.3},
            ],
        )

        body = captured["body"]
        assert len(body["spaceEmbedders"]) == 2
        assert body["spaceEmbedders"][0]["embedderId"] == "emb-large"
        assert body["spaceEmbedders"][0]["defaultRetrievalWeight"] == 0.7
        assert body["spaceEmbedders"][1]["embedderId"] == "emb-small"
        assert body["spaceEmbedders"][1]["defaultRetrievalWeight"] == 0.3

        assert len(result.space_embedders) == 2


# ---------------------------------------------------------------------------
# Memory CRUD
# ---------------------------------------------------------------------------

class TestMemoryCRUD:

    def test_get_memory(self, mock_client_factory):
        captured = {}

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

        client = mock_client_factory(handler)
        result = client.memories.get(id="mem-123")

        assert captured["method"] == "GET"
        assert "/v1/memories/mem-123" in captured["url"]
        assert result.memory_id == "mem-123"

    def test_delete_memory(self, mock_client_factory):
        captured = {}

        def handler(request: httpx.Request) -> httpx.Response:
            captured["method"] = request.method
            captured["url"] = str(request.url)
            return httpx.Response(204)

        client = mock_client_factory(handler)
        client.memories.delete(id="mem-123")

        assert captured["method"] == "DELETE"
        assert "/v1/memories/mem-123" in captured["url"]

    def test_list_memories_uses_space_path(self, mock_client_factory):
        """Memories list uses /v1/spaces/{space_id}/memories path."""
        captured = {}
        list_response = {"memories": [MEMORY_RESPONSE]}

        def handler(request: httpx.Request) -> httpx.Response:
            captured["method"] = request.method
            captured["url"] = str(request.url)
            return httpx.Response(200, json=list_response)

        client = mock_client_factory(handler)
        items = list(client.memories.list(space_id="space-456"))

        assert captured["method"] == "GET"
        assert "/v1/spaces/space-456/memories" in captured["url"]
        assert len(items) == 1

    def test_content_returns_bytes(self, mock_client_factory):
        captured = {}

        def handler(request: httpx.Request) -> httpx.Response:
            captured["method"] = request.method
            captured["url"] = str(request.url)
            return httpx.Response(200, content=b"Hello, world!")

        client = mock_client_factory(handler)
        content = client.memories.content(id="mem-123")

        assert captured["method"] == "GET"
        assert "/v1/memories/mem-123/content" in captured["url"]
        assert content == b"Hello, world!"


# ---------------------------------------------------------------------------
# Error handling on CRUD
# ---------------------------------------------------------------------------

class TestCRUDErrors:

    def test_get_404_raises_not_found(self, mock_client_factory):
        from goodmem.errors import NotFoundError

        def handler(request: httpx.Request) -> httpx.Response:
            return httpx.Response(404, json={"message": "Not found"})

        client = mock_client_factory(handler)
        with pytest.raises(NotFoundError):
            client.embedders.get(id="nonexistent")

    def test_create_409_raises_conflict(self, mock_client_factory):
        from goodmem.errors import ConflictError

        def handler(request: httpx.Request) -> httpx.Response:
            return httpx.Response(409, json={"message": "Already exists"})

        client = mock_client_factory(handler)
        with pytest.raises(ConflictError):
            client.embedders.create(
                display_name="Dup",
                model_identifier="text-embedding-3-small",
                api_key="sk-test",
            )

    def test_delete_404_raises_not_found(self, mock_client_factory):
        from goodmem.errors import NotFoundError

        def handler(request: httpx.Request) -> httpx.Response:
            return httpx.Response(404, json={"message": "Not found"})

        client = mock_client_factory(handler)
        with pytest.raises(NotFoundError):
            client.spaces.delete(id="nonexistent")

    def test_update_404_raises_not_found(self, mock_client_factory):
        from goodmem.errors import NotFoundError

        def handler(request: httpx.Request) -> httpx.Response:
            return httpx.Response(404, json={"message": "Not found"})

        client = mock_client_factory(handler)
        with pytest.raises(NotFoundError):
            client.llms.update(id="nonexistent", request=LLMUpdateRequest(display_name="Test"))


# ---------------------------------------------------------------------------
# Retrieve with reranker_id
# ---------------------------------------------------------------------------

class TestRetrieveWithReranker:

    def test_retrieve_body_with_reranker_only(self):
        from goodmem.api.memories import _build_retrieve_body

        body = _build_retrieve_body("query", {
            "space_ids": ["s1"],
            "reranker_id": "rr-1",
        })
        assert "postProcessor" in body
        pp = body["postProcessor"]
        assert pp["config"]["reranker_id"] == "rr-1"
        assert "llm_id" not in pp["config"]

    def test_retrieve_body_with_reranker_and_llm(self):
        from goodmem.api.memories import _build_retrieve_body

        body = _build_retrieve_body("query", {
            "space_ids": ["s1"],
            "llm_id": "llm-1",
            "reranker_id": "rr-1",
        })
        pp = body["postProcessor"]
        assert pp["config"]["llm_id"] == "llm-1"
        assert pp["config"]["reranker_id"] == "rr-1"

    def test_retrieve_body_with_all_post_processor_args(self):
        from goodmem.api.memories import _build_retrieve_body

        body = _build_retrieve_body("query", {
            "space_ids": ["s1"],
            "llm_id": "llm-1",
            "reranker_id": "rr-1",
            "prompt": "custom prompt",
            "sys_prompt": "system prompt",
            "gen_token_budget": 500,
            "relevance_threshold": 0.7,
            "llm_temp": 0.3,
            "max_results": 10,
            "chronological_resort": True,
        })
        pp = body["postProcessor"]
        config = pp["config"]
        assert config["llm_id"] == "llm-1"
        assert config["reranker_id"] == "rr-1"
        assert config["prompt"] == "custom prompt"
        assert config["sys_prompt"] == "system prompt"
        assert config["gen_token_budget"] == 500
        assert config["relevance_threshold"] == 0.7
        assert config["llm_temp"] == 0.3
        assert config["max_results"] == 10
        assert config["chronological_resort"] is True
