"""Lightweight GraphQL-style query layer for NEUROVAULT v0.3."""

from __future__ import annotations

from dataclasses import dataclass
import re
from typing import Any

try:  # pragma: no cover - optional dependency path
    from graphql import OperationType, parse as gql_parse

    _HAS_GRAPHQL_CORE = True
except Exception:  # pragma: no cover - lightweight fallback path
    gql_parse = None  # type: ignore[assignment]
    OperationType = None  # type: ignore[assignment]
    _HAS_GRAPHQL_CORE = False


@dataclass
class _ParsedOperation:
    operation_type: str
    fields: list[str]


class GraphQLService:
    """Handle a constrained GraphQL subset for core operations."""

    def __init__(self, engine):
        self._engine = engine

    def execute(self, query: str, variables: dict[str, Any] | None = None) -> dict[str, Any]:
        try:
            parsed = self._parse_operation(str(query or ""))
            vars_map = variables or {}
            if parsed.operation_type == "mutation":
                return self._execute_mutation(parsed, vars_map)
            return self._execute_query(parsed, vars_map)
        except Exception as exc:
            return {"errors": [{"message": str(exc)}]}

    @staticmethod
    def _strip_comments(text: str) -> str:
        return re.sub(r"#.*", "", text)

    def _parse_operation(self, query: str) -> _ParsedOperation:
        text = self._strip_comments(query).strip()
        if not text:
            raise ValueError("Empty GraphQL query")

        if _HAS_GRAPHQL_CORE and gql_parse is not None and OperationType is not None:
            try:
                doc = gql_parse(text)
            except Exception as exc:
                raise ValueError(f"Invalid GraphQL syntax: {exc}") from exc
            for definition in getattr(doc, "definitions", []):
                op = getattr(definition, "operation", None)
                selection_set = getattr(definition, "selection_set", None)
                if op is None or selection_set is None:
                    continue
                if op == OperationType.QUERY:
                    op_name = "query"
                elif op == OperationType.MUTATION:
                    op_name = "mutation"
                else:
                    raise ValueError("Unsupported GraphQL operation type")
                fields: list[str] = []
                for node in getattr(selection_set, "selections", []):
                    name_node = getattr(node, "name", None)
                    value = getattr(name_node, "value", None)
                    if isinstance(value, str) and value:
                        fields.append(value)
                if fields:
                    return _ParsedOperation(operation_type=op_name, fields=fields)
            raise ValueError("GraphQL query does not request any root fields")

        open_brace = text.find("{")
        if open_brace < 0:
            raise ValueError("GraphQL query must contain a selection set")

        prefix = text[:open_brace].strip()
        operation_type = "query"
        if prefix:
            op_token = prefix.split(None, 1)[0].lower()
            if op_token in {"query", "mutation"}:
                operation_type = op_token
            else:
                raise ValueError("Unsupported GraphQL operation header")

        selection = self._extract_balanced_block(text, open_brace)
        fields = self._extract_top_level_fields(selection)
        if not fields:
            raise ValueError("GraphQL query does not request any root fields")
        return _ParsedOperation(operation_type=operation_type, fields=fields)

    @staticmethod
    def _extract_balanced_block(text: str, start: int) -> str:
        depth = 0
        in_string = False
        escaped = False
        quote = '"'

        for i in range(start, len(text)):
            ch = text[i]
            if in_string:
                if escaped:
                    escaped = False
                    continue
                if ch == "\\":
                    escaped = True
                    continue
                if ch == quote:
                    in_string = False
                continue

            if ch in {"'", '"'}:
                in_string = True
                quote = ch
                continue
            if ch == "{":
                depth += 1
            elif ch == "}":
                depth -= 1
                if depth == 0:
                    return text[start : i + 1]

        raise ValueError("Unbalanced GraphQL selection set")

    @staticmethod
    def _extract_top_level_fields(selection: str) -> list[str]:
        if not selection.startswith("{") or not selection.endswith("}"):
            return []

        fields: list[str] = []
        depth = 0
        paren_depth = 0
        bracket_depth = 0
        in_string = False
        escaped = False
        quote = '"'
        i = 0
        text = selection

        def _read_identifier(pos: int) -> tuple[str, int]:
            j = pos
            while j < len(text) and (text[j].isalnum() or text[j] == "_"):
                j += 1
            return text[pos:j], j

        while i < len(text):
            ch = text[i]
            if in_string:
                if escaped:
                    escaped = False
                elif ch == "\\":
                    escaped = True
                elif ch == quote:
                    in_string = False
                i += 1
                continue

            if ch in {"'", '"'}:
                in_string = True
                quote = ch
                i += 1
                continue

            if ch == "{":
                depth += 1
                i += 1
                continue
            if ch == "}":
                depth -= 1
                i += 1
                continue
            if ch == "(":
                paren_depth += 1
                i += 1
                continue
            if ch == ")":
                paren_depth = max(0, paren_depth - 1)
                i += 1
                continue
            if ch == "[":
                bracket_depth += 1
                i += 1
                continue
            if ch == "]":
                bracket_depth = max(0, bracket_depth - 1)
                i += 1
                continue

            if (
                depth == 1
                and paren_depth == 0
                and bracket_depth == 0
                and (ch.isalpha() or ch == "_")
            ):
                first, j = _read_identifier(i)
                k = j
                while k < len(text) and text[k].isspace():
                    k += 1

                field_name = first
                if k < len(text) and text[k] == ":":
                    k += 1
                    while k < len(text) and text[k].isspace():
                        k += 1
                    if k < len(text) and (text[k].isalpha() or text[k] == "_"):
                        aliased, k2 = _read_identifier(k)
                        if aliased:
                            field_name = aliased
                            j = k2
                if field_name:
                    fields.append(field_name)
                i = max(j, i + 1)
                continue

            i += 1
        return fields

    def _execute_query(self, parsed: _ParsedOperation, variables: dict[str, Any]) -> dict[str, Any]:
        allowed = {"stats", "recall", "entities", "status"}
        unknown = [field for field in parsed.fields if field not in allowed]
        if unknown:
            return {"errors": [{"message": f"Unsupported root fields: {', '.join(unknown)}"}]}

        data: dict[str, Any] = {}
        if "stats" in parsed.fields:
            data["stats"] = self._stats_payload()
        if "recall" in parsed.fields:
            data["recall"] = self._recall_payload(variables)
        if "entities" in parsed.fields:
            data["entities"] = self._entities_payload(variables)
        if "status" in parsed.fields:
            status = self._engine.status
            data["status"] = {
                "daemonRunning": bool(status.daemon_running),
                "entityCount": int(status.entity_count),
                "relationshipCount": int(status.relationship_count),
                "insightCount": int(status.insight_count),
            }
        return {"data": data}

    def _execute_mutation(self, parsed: _ParsedOperation, variables: dict[str, Any]) -> dict[str, Any]:
        if parsed.fields != ["ingest"]:
            return {"errors": [{"message": "Supported mutation root field: ingest"}]}
        return {"data": {"ingest": self._mutation_ingest(variables)}}

    def _stats_payload(self) -> dict[str, Any]:
        stats = self._engine.stats()
        return {
            "memoryCount": int(stats.get("memory_count", 0)),
            "entityCount": int(stats.get("entity_count", 0)),
            "relationshipCount": int(stats.get("relationship_count", 0)),
            "clusterCount": int(stats.get("cluster_count", 0)),
            "insightCount": int(stats.get("insight_count", 0)),
            "pluginCount": int(stats.get("plugin_count", 0)),
        }

    def _recall_payload(self, variables: dict[str, Any]) -> list[dict[str, Any]]:
        query = str(variables.get("query", variables.get("q", ""))).strip()
        if not query:
            return []
        limit = int(variables.get("limit", 10))
        mode = str(variables.get("mode", "multi"))
        rows = self._engine.recall(query=query, limit=limit, mode=mode)
        return [
            {
                "id": row.memory.id.hex(),
                "content": row.memory.content,
                "score": float(row.score),
                "type": str(row.match_type),
                "source": str(row.memory.source),
            }
            for row in rows
        ]

    def _entities_payload(self, variables: dict[str, Any]) -> list[dict[str, Any]]:
        limit = int(variables.get("limit", 50))
        entity_type = variables.get("entityType")
        rows = self._engine.get_entities(entity_type=entity_type, limit=limit)
        return [
            {
                "id": row.id.hex() if row.id else "",
                "name": row.name,
                "entityType": row.entity_type,
                "importance": float(row.importance),
                "mentionCount": int(row.mention_count),
            }
            for row in rows
        ]

    def _mutation_ingest(self, variables: dict[str, Any]) -> dict[str, Any]:
        content = str(variables.get("content", "")).strip()
        if not content:
            raise ValueError("mutation ingest requires variable 'content'")

        memory = self._engine.ingest(
            content=content,
            source=str(variables.get("source", "graphql")),
            importance=float(variables.get("importance", 0.5)),
            metadata=variables.get("metadata", {}) if isinstance(variables.get("metadata"), dict) else {},
        )
        if memory is None:
            raise ValueError("memory blocked by privacy filter")

        return {
            "id": memory.id.hex(),
            "content": memory.content,
            "source": memory.source,
            "importance": float(memory.importance),
        }
