"""Tests for the SDK→REST snippet translator."""
from __future__ import annotations

from pathlib import Path

import pytest


@pytest.fixture(scope="module")
def translations():
    from _doc_gen.sdk2rest import translate_all_snippets
    return translate_all_snippets(Path(__file__).parent.parent)


class TestTranslateAllSnippets:
    """Validate structural correctness of generated REST snippets."""

    def test_all_sdk_snippets_translated(self, translations):
        """Every sdk.* snippet should produce a rest.* snippet."""
        from _doc_gen.snippets import extract_marked_snippets, _integration_test_files

        script_dir = Path(__file__).parent.parent
        extracted = extract_marked_snippets(_integration_test_files(script_dir))
        sdk_ids = {k for k in extracted if k.startswith("sdk.")}
        rest_ids = set(translations.keys())

        assert len(rest_ids) == len(sdk_ids), (
            f"Expected {len(sdk_ids)} REST snippets, got {len(rest_ids)}"
        )

    def test_correct_http_methods(self, translations):
        """GET/POST/PUT/DELETE should match the expected pattern."""
        for rest_id, code in translations.items():
            parts = rest_id.split(".")
            method_name = parts[2] if len(parts) > 2 else ""

            if method_name == "delete":
                assert "httpx.delete(" in code, f"{rest_id}: expected DELETE"
            elif method_name in ("get", "list", "me", "info", "content",
                                  "pages", "pages_image"):
                assert "httpx.get(" in code or "httpx.stream(" in code, (
                    f"{rest_id}: expected GET"
                )
            elif method_name in ("create", "batch_create", "batch_delete",
                                  "batch_get", "retrieve", "init", "drain",
                                  "document"):
                assert "httpx.post(" in code or "httpx.stream(" in code, (
                    f"{rest_id}: expected POST"
                )
            elif method_name == "update":
                assert "httpx.put(" in code, f"{rest_id}: expected PUT"

    def test_camel_case_body_keys(self, translations):
        """Body JSON keys should be camelCase, not snake_case."""
        import re
        # Check that json= bodies don't contain snake_case keys
        # (except for known exceptions like x-api-key which is a header)
        for rest_id, code in translations.items():
            # Extract keys inside json={...} blocks
            for m in re.finditer(r'"([a-z][a-z0-9]*(?:_[a-z0-9]+)+)":', code):
                key = m.group(1)
                # These are values, not keys — skip things inside data payloads
                if key in ("x-api-key", "sdk-doc-test"):
                    continue
                # Flag any snake_case key in a json= block
                # Check if it's inside a json= assignment
                pos = m.start()
                preceding = code[:pos]
                if "json=" in preceding[max(0, pos - 200):]:
                    pytest.fail(
                        f"{rest_id}: snake_case key '{key}' in JSON body"
                    )

    def test_auth_header_present(self, translations):
        """Most endpoints should include x-api-key header."""
        no_auth_ok = {"rest.system.info"}
        for rest_id, code in translations.items():
            if rest_id in no_auth_ok:
                continue
            assert "x-api-key" in code, f"{rest_id}: missing auth header"

    def test_raise_for_status(self, translations):
        """Every snippet should call raise_for_status()."""
        for rest_id, code in translations.items():
            assert "raise_for_status()" in code, (
                f"{rest_id}: missing raise_for_status()"
            )

    def test_paginated_list_has_next_token_loop(self, translations):
        """Paginated list methods should have a nextToken loop; non-paginated should not."""
        _PAGINATED = {"rest.spaces.list", "rest.memories.list"}
        list_ids = [k for k in translations if k.endswith(".list")]
        for rest_id in list_ids:
            code = translations[rest_id]
            if rest_id in _PAGINATED:
                assert "nextToken" in code, f"{rest_id}: missing nextToken loop"
                assert "while True:" in code, f"{rest_id}: missing while loop"
            else:
                assert "nextToken" not in code, f"{rest_id}: should not have pagination"
                assert "while True:" not in code, f"{rest_id}: should not have while loop"

    def test_streaming_has_accept_header(self, translations):
        """Retrieve endpoints should include Accept: ndjson header."""
        for rest_id in ["rest.memories.retrieve", "rest.memories.retrieve.2"]:
            if rest_id in translations:
                code = translations[rest_id]
                assert "application/x-ndjson" in code, (
                    f"{rest_id}: missing Accept header"
                )

    def test_embedder_create_expands_convenience(self, translations):
        """Embedder create should expand model_identifier to provider fields."""
        code = translations.get("rest.embedders.create", "")
        assert '"providerType": "OPENAI"' in code
        assert '"endpointUrl"' in code
        assert '"dimensionality": 1536' in code
        assert '"distributionType": "DENSE"' in code

    def test_spaces_create_has_chunking_config(self, translations):
        """Spaces create should include default chunking config."""
        code = translations.get("rest.spaces.create", "")
        assert '"defaultChunkingConfig"' in code
        assert '"chunkSize": 512' in code

    def test_memories_create_has_content_type(self, translations):
        """Memories create (text) should include contentType."""
        code = translations.get("rest.memories.create", "")
        assert '"contentType": "text/plain"' in code

    def test_file_upload_uses_multipart(self, translations):
        """memories.create.2 (file) should use multipart."""
        code = translations.get("rest.memories.create.2", "")
        assert 'files=' in code or 'data=' in code
        assert 'open(file_path' in code
