"""Tests for GraphQLAdapter.introspect() — verifies that a mock GraphQL
introspection payload is correctly parsed into Brynq tool descriptors."""

import pytest

from runtime.adapters.graphql import GraphQLAdapter


# ── Mock introspection payload ───────────────────────────────────────────

MOCK_SCHEMA = {
    "queryType": {"name": "Query"},
    "mutationType": {"name": "Mutation"},
    "types": [
        {
            "name": "Query",
            "kind": "OBJECT",
            "fields": [
                {
                    "name": "users",
                    "description": "List all users",
                    "args": [
                        {
                            "name": "limit",
                            "description": "Max results",
                            "type": {"name": "Int", "kind": "SCALAR", "ofType": None},
                            "defaultValue": "10",
                        },
                        {
                            "name": "active",
                            "description": "",
                            "type": {
                                "name": None,
                                "kind": "NON_NULL",
                                "ofType": {"name": "Boolean", "kind": "SCALAR"},
                            },
                            "defaultValue": None,
                        },
                    ],
                    "type": {
                        "name": None,
                        "kind": "LIST",
                        "ofType": {"name": "User", "kind": "OBJECT"},
                    },
                },
                {
                    "name": "user",
                    "description": "Get a single user",
                    "args": [
                        {
                            "name": "id",
                            "description": "User ID",
                            "type": {
                                "name": None,
                                "kind": "NON_NULL",
                                "ofType": {"name": "ID", "kind": "SCALAR"},
                            },
                            "defaultValue": None,
                        },
                    ],
                    "type": {"name": "User", "kind": "OBJECT", "ofType": None},
                },
                {
                    "name": "healthCheck",
                    "description": None,
                    "args": [],
                    "type": {"name": "String", "kind": "SCALAR", "ofType": None},
                },
            ],
        },
        {
            "name": "Mutation",
            "kind": "OBJECT",
            "fields": [
                {
                    "name": "createUser",
                    "description": "Create a new user",
                    "args": [
                        {
                            "name": "name",
                            "description": "User name",
                            "type": {
                                "name": None,
                                "kind": "NON_NULL",
                                "ofType": {"name": "String", "kind": "SCALAR"},
                            },
                            "defaultValue": None,
                        },
                        {
                            "name": "email",
                            "description": "Email address",
                            "type": {"name": "String", "kind": "SCALAR", "ofType": None},
                            "defaultValue": None,
                        },
                    ],
                    "type": {"name": "User", "kind": "OBJECT", "ofType": None},
                },
                {
                    "name": "deleteUser",
                    "description": "Delete a user",
                    "args": [
                        {
                            "name": "id",
                            "description": "",
                            "type": {
                                "name": None,
                                "kind": "NON_NULL",
                                "ofType": {"name": "ID", "kind": "SCALAR"},
                            },
                            "defaultValue": None,
                        },
                    ],
                    "type": {"name": "Boolean", "kind": "SCALAR", "ofType": None},
                },
            ],
        },
        # Internal types — should NOT produce tools
        {"name": "User", "kind": "OBJECT", "fields": [
            {"name": "id", "description": None, "args": [],
             "type": {"name": "ID", "kind": "SCALAR", "ofType": None}},
            {"name": "name", "description": None, "args": [],
             "type": {"name": "String", "kind": "SCALAR", "ofType": None}},
        ]},
        {"name": "String", "kind": "SCALAR", "fields": None},
        {"name": "Int", "kind": "SCALAR", "fields": None},
        {"name": "Boolean", "kind": "SCALAR", "fields": None},
        {"name": "ID", "kind": "SCALAR", "fields": None},
    ],
}


@pytest.fixture
def adapter():
    """Create an adapter with schema pre-loaded (bypass network fetch)."""
    a = GraphQLAdapter("https://api.example.com/graphql")
    a._schema = MOCK_SCHEMA
    a._tools = GraphQLAdapter._extract_tools(MOCK_SCHEMA)
    return a


# ── introspect() structure ───────────────────────────────────────────────


class TestIntrospect:
    def test_returns_tools_list(self, adapter):
        result = adapter.introspect()
        assert "tools" in result
        assert isinstance(result["tools"], list)

    def test_returns_version(self, adapter):
        result = adapter.introspect()
        assert "version" in result
        assert isinstance(result["version"], str)

    def test_returns_endpoint(self, adapter):
        result = adapter.introspect()
        assert result["endpoint"] == "https://api.example.com/graphql"

    def test_extracts_correct_number_of_tools(self, adapter):
        tools = adapter.introspect()["tools"]
        # 3 queries (users, user, healthCheck) + 2 mutations (createUser, deleteUser)
        assert len(tools) == 5


# ── Query extraction ─────────────────────────────────────────────────────


class TestQueryExtraction:
    def test_query_names(self, adapter):
        tools = adapter.introspect()["tools"]
        queries = [t for t in tools if t["operation_type"] == "query"]
        names = {t["name"] for t in queries}
        assert names == {"users", "user", "healthCheck"}

    def test_query_description(self, adapter):
        tools = adapter.introspect()["tools"]
        by_name = {t["name"]: t for t in tools}
        assert by_name["users"]["description"] == "List all users"
        assert by_name["user"]["description"] == "Get a single user"

    def test_description_falls_back_to_name(self, adapter):
        tools = adapter.introspect()["tools"]
        by_name = {t["name"]: t for t in tools}
        # healthCheck has description=None → falls back to field name
        assert by_name["healthCheck"]["description"] == "healthCheck"

    def test_query_return_type(self, adapter):
        tools = adapter.introspect()["tools"]
        by_name = {t["name"]: t for t in tools}
        assert by_name["users"]["return_type"] == "[User]"
        assert by_name["user"]["return_type"] == "User"
        assert by_name["healthCheck"]["return_type"] == "String"

    def test_query_parameters(self, adapter):
        tools = adapter.introspect()["tools"]
        by_name = {t["name"]: t for t in tools}
        params = by_name["users"]["parameters"]
        assert len(params) == 2

        limit_param = next(p for p in params if p["name"] == "limit")
        assert limit_param["type"] == "Int"
        assert limit_param["required"] is False

        active_param = next(p for p in params if p["name"] == "active")
        assert active_param["type"] == "Boolean!"
        assert active_param["required"] is True
        assert active_param["description"] == ""

    def test_required_id_parameter(self, adapter):
        tools = adapter.introspect()["tools"]
        by_name = {t["name"]: t for t in tools}
        params = by_name["user"]["parameters"]
        assert len(params) == 1
        assert params[0]["name"] == "id"
        assert params[0]["required"] is True
        assert params[0]["type"] == "ID!"

    def test_no_args_produces_empty_parameters(self, adapter):
        tools = adapter.introspect()["tools"]
        by_name = {t["name"]: t for t in tools}
        assert by_name["healthCheck"]["parameters"] == []


# ── Mutation extraction ──────────────────────────────────────────────────


class TestMutationExtraction:
    def test_mutation_names(self, adapter):
        tools = adapter.introspect()["tools"]
        mutations = [t for t in tools if t["operation_type"] == "mutation"]
        names = {t["name"] for t in mutations}
        assert names == {"createUser", "deleteUser"}

    def test_mutation_description(self, adapter):
        tools = adapter.introspect()["tools"]
        by_name = {t["name"]: t for t in tools}
        assert by_name["createUser"]["description"] == "Create a new user"
        assert by_name["deleteUser"]["description"] == "Delete a user"

    def test_mutation_return_type(self, adapter):
        tools = adapter.introspect()["tools"]
        by_name = {t["name"]: t for t in tools}
        assert by_name["createUser"]["return_type"] == "User"
        assert by_name["deleteUser"]["return_type"] == "Boolean"

    def test_mutation_parameters(self, adapter):
        tools = adapter.introspect()["tools"]
        by_name = {t["name"]: t for t in tools}
        params = by_name["createUser"]["parameters"]
        assert len(params) == 2

        name_param = next(p for p in params if p["name"] == "name")
        assert name_param["required"] is True
        assert name_param["type"] == "String!"

        email_param = next(p for p in params if p["name"] == "email")
        assert email_param["required"] is False
        assert email_param["type"] == "String"

    def test_mutation_field_name_matches_name(self, adapter):
        tools = adapter.introspect()["tools"]
        by_name = {t["name"]: t for t in tools}
        assert by_name["createUser"]["field_name"] == "createUser"
        assert by_name["deleteUser"]["field_name"] == "deleteUser"


# ── Edge cases ───────────────────────────────────────────────────────────


class TestEdgeCases:
    def test_no_mutation_type(self):
        schema = {
            "queryType": {"name": "Query"},
            "mutationType": None,
            "types": [
                {
                    "name": "Query",
                    "kind": "OBJECT",
                    "fields": [
                        {
                            "name": "ping",
                            "description": "Ping",
                            "args": [],
                            "type": {"name": "String", "kind": "SCALAR", "ofType": None},
                        }
                    ],
                },
            ],
        }
        a = GraphQLAdapter("https://example.com/graphql")
        a._schema = schema
        a._tools = GraphQLAdapter._extract_tools(schema)
        tools = a.introspect()["tools"]
        assert len(tools) == 1
        assert tools[0]["name"] == "ping"
        assert tools[0]["operation_type"] == "query"

    def test_no_query_type(self):
        schema = {
            "queryType": None,
            "mutationType": {"name": "Mutation"},
            "types": [
                {
                    "name": "Mutation",
                    "kind": "OBJECT",
                    "fields": [
                        {
                            "name": "nuke",
                            "description": "Reset",
                            "args": [],
                            "type": {"name": "Boolean", "kind": "SCALAR", "ofType": None},
                        }
                    ],
                },
            ],
        }
        a = GraphQLAdapter("https://example.com/graphql")
        a._schema = schema
        a._tools = GraphQLAdapter._extract_tools(schema)
        tools = a.introspect()["tools"]
        assert len(tools) == 1
        assert tools[0]["operation_type"] == "mutation"

    def test_empty_types_list(self):
        schema = {
            "queryType": {"name": "Query"},
            "mutationType": None,
            "types": [],
        }
        a = GraphQLAdapter("https://example.com/graphql")
        a._schema = schema
        a._tools = GraphQLAdapter._extract_tools(schema)
        assert a.introspect()["tools"] == []

    def test_missing_types_key(self):
        schema = {
            "queryType": {"name": "Query"},
            "mutationType": None,
        }
        a = GraphQLAdapter("https://example.com/graphql")
        a._schema = schema
        a._tools = GraphQLAdapter._extract_tools(schema)
        assert a.introspect()["tools"] == []

    def test_field_with_no_name_skipped(self):
        schema = {
            "queryType": {"name": "Query"},
            "mutationType": None,
            "types": [
                {
                    "name": "Query",
                    "kind": "OBJECT",
                    "fields": [
                        {"description": "orphan", "args": []},  # no "name"
                        {
                            "name": "valid",
                            "description": "OK",
                            "args": [],
                            "type": {"name": "String", "kind": "SCALAR", "ofType": None},
                        },
                    ],
                },
            ],
        }
        a = GraphQLAdapter("https://example.com/graphql")
        a._schema = schema
        a._tools = GraphQLAdapter._extract_tools(schema)
        tools = a.introspect()["tools"]
        assert len(tools) == 1
        assert tools[0]["name"] == "valid"

    def test_non_null_return_type(self):
        schema = {
            "queryType": {"name": "Query"},
            "mutationType": None,
            "types": [
                {
                    "name": "Query",
                    "kind": "OBJECT",
                    "fields": [
                        {
                            "name": "requiredField",
                            "description": "Always returns",
                            "args": [],
                            "type": {
                                "name": None,
                                "kind": "NON_NULL",
                                "ofType": {"name": "String", "kind": "SCALAR"},
                            },
                        }
                    ],
                },
            ],
        }
        a = GraphQLAdapter("https://example.com/graphql")
        a._schema = schema
        a._tools = GraphQLAdapter._extract_tools(schema)
        tools = a.introspect()["tools"]
        assert tools[0]["return_type"] == "String!"
