"""
Pipeline Definition Validator for Aiko Services

This module provides validation functions for Aiko Services pipeline definitions
to ensure structural correctness before deployment, plus optional cloud-backed
UUID verification for agent check workflows.
"""

from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional, Set, Tuple
from uuid import UUID

from highlighter.client.gql_base import get_gql_obj, to_gql_type

UUID_KEYWORDS = {
    "uuid",
    "account_uuid",
    "data_source_uuid",
    "object_class_id",
    "attribute_id",
    "attribute_enum_id",
}

UUID_FIELD_MAP = {
    "account_uuid": "Account",
    "data_source_uuid": "DataSource",
    "object_class_id": "ObjectClass",
    "attribute_id": "Attribute",
    "attribute_enum_id": "AttributeEnum",
    "training_run_artefact_id": "TrainingRunArtefact",
}

ENTITY_TYPE_ALIASES = {
    "Account": ["Account", "AccountType"],
    "DataSource": ["DataSource", "DataSourceType"],
    "ObjectClass": ["ObjectClass", "ObjectClassType"],
    "Attribute": [
        "Attribute",
        "AttributeType",
        "AttributeDefinition",
        "AttributeDefinitionType",
        "EntityAttributeType",
    ],
    "AttributeEnum": [
        "AttributeEnum",
        "AttributeEnumType",
        "AttributeEnumDefinition",
        "AttributeEnumDefinitionType",
        "EntityAttributeEnumType",
    ],
    "TrainingRunArtefact": ["TrainingRunArtefact", "TrainingRunArtefactType"],
}


@dataclass
class ValidationError:
    """Represents a single validation error."""

    severity: str  # "error" or "warning"
    message: str
    location: Optional[str] = None  # e.g., "graph[0]", "elements[2].input[0]"
    context: Optional[Dict[str, Any]] = None  # Additional context information


@dataclass
class ValidationResult:
    """Result of a validation operation."""

    is_valid: bool
    errors: List[ValidationError] = field(default_factory=list)
    warnings: List[ValidationError] = field(default_factory=list)

    def add_error(
        self, message: str, location: Optional[str] = None, context: Optional[Dict[str, Any]] = None
    ):
        """Add an error to the validation result."""
        self.errors.append(
            ValidationError(severity="error", message=message, location=location, context=context)
        )
        self.is_valid = False

    def add_warning(
        self, message: str, location: Optional[str] = None, context: Optional[Dict[str, Any]] = None
    ):
        """Add a warning to the validation result."""
        self.warnings.append(
            ValidationError(severity="warning", message=message, location=location, context=context)
        )

    def merge(self, other: "ValidationResult"):
        """Merge another validation result into this one."""
        self.errors.extend(other.errors)
        self.warnings.extend(other.warnings)
        if not other.is_valid:
            self.is_valid = False


@dataclass
class CloudReference:
    path: str
    key: str
    uuid: str
    entity_type: str
    hint: Optional[str] = None


@dataclass
class CloudValidationDetails:
    validated: List[CloudReference] = field(default_factory=list)
    missing: List[CloudReference] = field(default_factory=list)
    invalid: List[CloudReference] = field(default_factory=list)
    unsupported: List[CloudReference] = field(default_factory=list)
    checks: List[Dict[str, Any]] = field(default_factory=list)


def _parse_graph_expression(graph_expr: str) -> List[Tuple[str, List[Tuple[str, str]]]]:
    """
    Parse a graph expression into a list of (element_name, input_mappings).

    Graph format:
        "(Element1 Element2 (Element1.output: input) Element3 (Element2.output: input))"

    Returns:
        List of tuples: [(element_name, [(source.output, target_input), ...])]
    """
    graph_expr = graph_expr.strip()
    if graph_expr.startswith("(") and graph_expr.endswith(")"):
        graph_expr = graph_expr[1:-1].strip()

    result = []
    tokens = []
    current_token_chars: List[str] = []
    paren_depth = 0

    for char in graph_expr:
        if char == "(":
            paren_depth += 1
            current_token_chars.append(char)
        elif char == ")":
            paren_depth -= 1
            current_token_chars.append(char)
        elif char.isspace() and paren_depth == 0:
            if current_token_chars:
                tokens.append("".join(current_token_chars))
                current_token_chars = []
        else:
            current_token_chars.append(char)

    if current_token_chars:
        tokens.append("".join(current_token_chars))

    i = 0
    while i < len(tokens):
        token = tokens[i]

        if token.startswith("("):
            if not result:
                i += 1
                continue

            mapping_str = token.strip("()")
            mappings = []

            parts = mapping_str.split()
            i_parts = 0
            while i_parts < len(parts):
                part = parts[i_parts]
                if ":" in part:
                    if part.endswith(":"):
                        source = part[:-1].strip()
                        if i_parts + 1 < len(parts):
                            target = parts[i_parts + 1].strip()
                            mappings.append((source, target))
                            i_parts += 2
                        else:
                            i_parts += 1
                    else:
                        source, target = part.split(":", 1)
                        mappings.append((source.strip(), target.strip()))
                        i_parts += 1
                else:
                    i_parts += 1

            element_name, existing_mappings = result[-1]
            result[-1] = (element_name, existing_mappings + mappings)
        else:
            result.append((token, []))

        i += 1

    return result


def _validate_graph_structure(
    pipeline_def: Dict[str, Any], elements_by_name: Dict[str, Dict[str, Any]]
) -> ValidationResult:
    result = ValidationResult(is_valid=True)

    if "graph" not in pipeline_def:
        result.add_error("Pipeline definition missing 'graph' key")
        return result

    graph = pipeline_def.get("graph")
    if not isinstance(graph, list):
        result.add_error("'graph' must be a list")
        return result
    if not graph:
        result.add_error("'graph' must not be empty")
        return result

    referenced_elements = set()

    for graph_idx, graph_expr in enumerate(graph):
        if not isinstance(graph_expr, str):
            result.add_error(
                f"Graph expression must be a string, got {type(graph_expr).__name__}",
                location=f"graph[{graph_idx}]",
            )
            continue

        try:
            parsed = _parse_graph_expression(graph_expr)
        except Exception as e:
            result.add_error(
                f"Failed to parse graph expression: {str(e)}",
                location=f"graph[{graph_idx}]",
                context={"expression": graph_expr},
            )
            continue

        for element_name, input_mappings in parsed:
            referenced_elements.add(element_name)

            if element_name not in elements_by_name:
                result.add_error(
                    f"Graph references undefined element '{element_name}'",
                    location=f"graph[{graph_idx}]",
                    context={"element": element_name},
                )
                continue

            element = elements_by_name[element_name]
            element_inputs = {
                inp["name"]: inp
                for inp in element.get("input", [])
                if isinstance(inp, dict) and "name" in inp
            }

            for source_ref, target_input in input_mappings:
                if "." not in source_ref:
                    result.add_error(
                        f"Invalid source reference '{source_ref}' - expected format 'ElementName.output_name'",
                        location=f"graph[{graph_idx}]",
                        context={"element": element_name, "source": source_ref},
                    )
                    continue

                source_element, source_output = source_ref.split(".", 1)

                if source_element not in elements_by_name:
                    result.add_error(
                        f"Source element '{source_element}' not found in elements",
                        location=f"graph[{graph_idx}]",
                        context={
                            "element": element_name,
                            "source_element": source_element,
                            "source_output": source_output,
                        },
                    )
                    continue

                source_elem_def = elements_by_name[source_element]
                source_outputs = {
                    out["name"]: out
                    for out in source_elem_def.get("output", [])
                    if isinstance(out, dict) and "name" in out
                }

                if source_output not in source_outputs:
                    result.add_error(
                        f"Source element '{source_element}' does not have output '{source_output}'",
                        location=f"graph[{graph_idx}]",
                        context={
                            "element": element_name,
                            "source_element": source_element,
                            "source_output": source_output,
                            "available_outputs": list(source_outputs.keys()),
                        },
                    )
                    continue

                if target_input not in element_inputs:
                    result.add_error(
                        f"Element '{element_name}' does not have input '{target_input}'",
                        location=f"graph[{graph_idx}]",
                        context={
                            "element": element_name,
                            "target_input": target_input,
                            "available_inputs": list(element_inputs.keys()),
                        },
                    )
                    continue

                source_type = source_outputs[source_output].get("type")
                target_type = element_inputs[target_input].get("type")

                if source_type is not None and target_type is not None:
                    normalized_source = str(source_type).strip()
                    normalized_target = str(target_type).strip()

                    if normalized_source != normalized_target:
                        result.add_error(
                            f"Type mismatch in connection: {source_element}.{source_output} ({source_type}) "
                            f"-> {element_name}.{target_input} ({target_type})",
                            location=f"graph[{graph_idx}]",
                            context={
                                "source_element": source_element,
                                "source_output": source_output,
                                "source_type": source_type,
                                "target_element": element_name,
                                "target_input": target_input,
                                "target_type": target_type,
                            },
                        )

    all_elements = set(elements_by_name.keys())
    unreferenced = all_elements - referenced_elements

    if unreferenced:
        result.add_warning(
            f"Elements defined but not referenced in graph: {', '.join(sorted(unreferenced))}",
            location="elements",
        )

    return result


def _validate_elements(pipeline_def: Dict[str, Any]) -> Tuple[ValidationResult, Dict[str, Dict[str, Any]]]:
    result = ValidationResult(is_valid=True)
    elements_by_name = {}

    elements = pipeline_def.get("elements", [])
    if not elements:
        result.add_error("Pipeline definition missing 'elements' key or elements list is empty")
        return result, elements_by_name

    if not isinstance(elements, list):
        result.add_error("'elements' must be a list")
        return result, elements_by_name

    for idx, element in enumerate(elements):
        if not isinstance(element, dict):
            result.add_error(
                f"Element must be a dictionary, got {type(element).__name__}", location=f"elements[{idx}]"
            )
            continue

        name = element.get("name")
        if not name:
            result.add_error("Element missing required 'name' field", location=f"elements[{idx}]")
            continue

        if name in elements_by_name:
            result.add_error(
                f"Duplicate element name '{name}'", location=f"elements[{idx}]", context={"element": name}
            )
            continue

        if "input" not in element:
            result.add_error(f"Element '{name}' missing 'input' field", location=f"elements[{idx}]")
        elif not isinstance(element["input"], list):
            result.add_error(f"Element '{name}' 'input' must be a list", location=f"elements[{idx}]")

        if "output" not in element:
            result.add_error(f"Element '{name}' missing 'output' field", location=f"elements[{idx}]")
        elif not isinstance(element["output"], list):
            result.add_error(f"Element '{name}' 'output' must be a list", location=f"elements[{idx}]")

        if "deploy" not in element:
            result.add_error(f"Element '{name}' missing 'deploy' field", location=f"elements[{idx}]")
        elif not isinstance(element["deploy"], dict):
            result.add_error(f"Element '{name}' 'deploy' must be a dictionary", location=f"elements[{idx}]")
        elif "local" not in element["deploy"]:
            result.add_error(
                f"Element '{name}' 'deploy' missing 'local' configuration", location=f"elements[{idx}].deploy"
            )
        elif "module" not in element["deploy"]["local"]:
            result.add_error(
                f"Element '{name}' 'deploy.local' missing 'module' field",
                location=f"elements[{idx}].deploy.local",
            )

        for input_idx, inp in enumerate(element.get("input", [])):
            if not isinstance(inp, dict):
                result.add_error("Input must be a dictionary", location=f"elements[{idx}].input[{input_idx}]")
                continue
            if "name" not in inp:
                result.add_error("Input missing 'name' field", location=f"elements[{idx}].input[{input_idx}]")
            if "type" not in inp:
                result.add_error("Input missing 'type' field", location=f"elements[{idx}].input[{input_idx}]")

        for output_idx, out in enumerate(element.get("output", [])):
            if not isinstance(out, dict):
                result.add_error(
                    "Output must be a dictionary", location=f"elements[{idx}].output[{output_idx}]"
                )
                continue
            if "name" not in out:
                result.add_error(
                    "Output missing 'name' field", location=f"elements[{idx}].output[{output_idx}]"
                )
            if "type" not in out:
                result.add_error(
                    "Output missing 'type' field", location=f"elements[{idx}].output[{output_idx}]"
                )

        elements_by_name[name] = element

    return result, elements_by_name


def validate_pipeline_definition_structure(pipeline_def: Dict[str, Any]) -> ValidationResult:
    result = ValidationResult(is_valid=True)

    required_keys = ["version", "name", "runtime", "graph", "elements"]
    for key in required_keys:
        if key not in pipeline_def:
            result.add_error(f"Pipeline definition missing required key '{key}'")

    version = pipeline_def.get("version")
    if version is not None and not isinstance(version, int):
        result.add_error(f"'version' must be an integer, got {type(version).__name__}")

    name = pipeline_def.get("name")
    if name is not None and not isinstance(name, str):
        result.add_error(f"'name' must be a string, got {type(name).__name__}")

    runtime = pipeline_def.get("runtime")
    if runtime is not None and not isinstance(runtime, str):
        result.add_error(f"'runtime' must be a string, got {type(runtime).__name__}")

    elements_result, elements_by_name = _validate_elements(pipeline_def)
    result.merge(elements_result)

    if elements_by_name:
        graph_result = _validate_graph_structure(pipeline_def, elements_by_name)
        result.merge(graph_result)

    return result


def _is_uuid_key(key: str) -> bool:
    key_lower = key.lower()
    if key_lower in UUID_FIELD_MAP:
        return True
    return any(keyword in key_lower for keyword in UUID_KEYWORDS)


def _extract_uuid_fields(
    data: Any, parent_key: str = ""
) -> Tuple[List[Tuple[str, str, str]], List[Tuple[str, str, str]]]:
    valid = []
    invalid = []

    if isinstance(data, dict):
        for key, value in data.items():
            current_path = f"{parent_key}.{key}" if parent_key else key

            if isinstance(value, str):
                if _is_uuid_key(key):
                    try:
                        UUID(value)
                        valid.append((current_path, key, value))
                    except (ValueError, AttributeError):
                        invalid.append((current_path, key, value))
                else:
                    try:
                        UUID(value)
                        valid.append((current_path, key, value))
                    except (ValueError, AttributeError):
                        pass

            valid_nested, invalid_nested = _extract_uuid_fields(value, current_path)
            valid.extend(valid_nested)
            invalid.extend(invalid_nested)

    elif isinstance(data, list):
        for idx, item in enumerate(data):
            current_path = f"{parent_key}[{idx}]"
            valid_nested, invalid_nested = _extract_uuid_fields(item, current_path)
            valid.extend(valid_nested)
            invalid.extend(invalid_nested)

    return valid, invalid


def _schema_return_type_name(field: Dict[str, Any]) -> Optional[str]:
    type_obj = field.get("type")
    while type_obj:
        if type_obj.get("kind") == "OBJECT":
            return type_obj.get("name")
        type_obj = type_obj.get("ofType")
    return None


def _schema_field_names(schema: Dict[str, Any], type_name: str) -> Set[str]:
    for t in schema["__schema"]["types"]:
        if t["name"] == type_name:
            return {f["name"] for f in t.get("fields", [])}
    return set()


def _execute_uuid_query(client: Any, query_name: str, arg_name: str, uuid_value: str) -> bool:
    schema = client.schema
    _, target_gql_obj = get_gql_obj(schema, query_name)
    arg = next((a for a in target_gql_obj["args"] if a["name"] == arg_name), None)
    if arg is None:
        raise ValueError(f"Query '{query_name}' does not accept argument '{arg_name}'")

    arg_type = to_gql_type(arg["type"])
    return_type_name = _schema_return_type_name(target_gql_obj)
    field_names = _schema_field_names(schema, return_type_name) if return_type_name else set()

    selection = ["__typename"]
    if "uuid" in field_names:
        selection.append("uuid")
    if "id" in field_names:
        selection.append("id")

    selection_block = "\n        ".join(selection)
    query = (
        f"query ($value: {arg_type}) {{\n"
        f"    {query_name}({arg_name}: $value) {{\n"
        f"        {selection_block}\n"
        f"    }}\n"
        f"}}"
    )

    response = client.execute(query, variable_values={"value": uuid_value})
    result = response.get(query_name)
    return result is not None


def _resolve_uuid_target(path: str, key: str) -> Optional[str]:
    if key == "uuid" and "class_lookup" in path:
        return UUID_FIELD_MAP["object_class_id"]
    if key == "enum_id" and "class_lookup" in path:
        return UUID_FIELD_MAP["attribute_enum_id"]
    key_lower = key.lower()
    if key_lower in UUID_FIELD_MAP:
        return UUID_FIELD_MAP[key_lower]
    return None


def _find_query_candidates(schema: Dict[str, Any], entity_type: str) -> List[Tuple[str, str]]:
    type_names = ENTITY_TYPE_ALIASES.get(entity_type, [entity_type])
    query_fields = []
    for t in schema["__schema"]["types"]:
        if t["name"] == "Query":
            query_fields = t.get("fields", [])
            break

    candidates: List[Tuple[str, str]] = []
    for field in query_fields:
        return_type_name = _schema_return_type_name(field)
        if return_type_name not in type_names:
            continue
        arg_names = [arg["name"] for arg in field.get("args", [])]
        for arg_name in ("uuid", "id"):
            if arg_name in arg_names:
                candidates.append((field["name"], arg_name))
                break

    if candidates:
        return candidates

    entity_hint = entity_type.lower()
    for field in query_fields:
        field_name = field["name"]
        if entity_hint not in field_name.lower():
            continue
        arg_names = [arg["name"] for arg in field.get("args", [])]
        for arg_name in ("uuid", "id"):
            if arg_name in arg_names:
                candidates.append((field_name, arg_name))
                break

    return candidates


def _get_value_by_path(data: Any, path: str) -> Any:
    """Resolve a dotted/indexed path produced by _extract_uuid_fields.

    The path format is intentionally narrow (e.g. "a.b[0].c") and does not
    support keys containing '.' or '['. This is acceptable because the paths
    are generated from the pipeline definition structure, not user-provided.
    """
    if not path:
        return data

    current = data
    token = ""  # nosec B105
    i = 0
    while i < len(path):
        char = path[i]
        if char == ".":
            if token:
                if not isinstance(current, dict) or token not in current:
                    return None
                current = current[token]
                token = ""  # nosec B105
            i += 1
            continue
        if char == "[":
            if token:
                if not isinstance(current, dict) or token not in current:
                    return None
                current = current[token]
                token = ""  # nosec B105
            end = path.find("]", i)
            if end == -1:
                return None
            index_str = path[i + 1 : end]
            if not index_str.isdigit():
                return None
            idx = int(index_str)
            if not isinstance(current, list) or idx >= len(current):
                return None
            current = current[idx]
            i = end + 1
            continue
        token += char
        i += 1

    if token:
        if not isinstance(current, dict) or token not in current:
            return None
        current = current[token]

    return current


def _find_first_value(data: Any, key: str) -> Optional[Any]:
    if isinstance(data, dict):
        if key in data:
            return data[key]
        for value in data.values():
            found = _find_first_value(value, key)
            if found is not None:
                return found
    elif isinstance(data, list):
        for item in data:
            found = _find_first_value(item, key)
            if found is not None:
                return found
    return None


def _validate_current_account(client: Any, account_uuid: str) -> Tuple[bool, Optional[str]]:
    query = "query { currentAccount { uuid } }"
    response = client.execute(query)
    account = response.get("currentAccount")
    if not account or not account.get("uuid"):
        return False, "currentAccount returned no uuid"
    return str(account["uuid"]) == account_uuid, None


def _validate_entity_attribute(client: Any, attribute_id: str) -> Tuple[bool, Optional[str]]:
    query = "query ($value: ID!) { entityAttribute(id: $value) { id } }"
    response = client.execute(query, variable_values={"value": attribute_id})
    return response.get("entityAttribute") is not None, None


def _validate_attribute_enum(
    client: Any,
    enum_uuid: str,
    pipeline_def: Dict[str, Any],
    path: str,
) -> Tuple[bool, str, Optional[str]]:
    parent_path = path.rsplit(".", 1)[0] if "." in path else ""
    parent = _get_value_by_path(pipeline_def, parent_path)
    attribute_id = None
    if isinstance(parent, dict):
        attribute_id = parent.get("attribute_id")

    object_class_id = _find_first_value(pipeline_def, "object_class_id")
    workflow_id = _find_first_value(pipeline_def, "workflow_id")

    if attribute_id and object_class_id and workflow_id:
        query = (
            "query ($selections: [EntityAttributeSelectionInputType!]!,"
            " $outputEntityAttributeId: ID!, $objectClassId: ID!, $workflowId: ID!) {"
            " entityAttributeEnums("
            " entityAttributeSelections: $selections,"
            " outputEntityAttributeId: $outputEntityAttributeId,"
            " objectClassId: $objectClassId,"
            " workflowId: $workflowId"
            " ) { entityAttributeCollections } }"
        )
        variables = {
            "selections": [{"entityAttributeEnumId": enum_uuid}],
            "outputEntityAttributeId": attribute_id,
            "objectClassId": object_class_id,
            "workflowId": workflow_id,
        }
        response = client.execute(query, variable_values=variables)
        collections = response.get("entityAttributeEnums", {}).get("entityAttributeCollections")
        if collections is None:
            return False, "entityAttributeEnums", "no entityAttributeCollections returned"
        payload = str(collections)
        return enum_uuid in payload, "entityAttributeEnums", None

    query = "query { entityAttributes { nodes { id entityAttributeEnums { id } } } }"
    response = client.execute(query)
    attributes = response.get("entityAttributes", {}).get("nodes") or []
    for attr in attributes:
        for enum in attr.get("entityAttributeEnums") or []:
            if str(enum.get("id")) == enum_uuid:
                return True, "entityAttributes", None
    return False, "entityAttributes", None


def validate_pipeline_definition_cloud(
    pipeline_def: Dict[str, Any], hl_client: Any
) -> Tuple[ValidationResult, CloudValidationDetails]:
    result = ValidationResult(is_valid=True)
    details = CloudValidationDetails()

    valid_fields, invalid_fields = _extract_uuid_fields(pipeline_def)

    for path, key, value in invalid_fields:
        details.invalid.append(
            CloudReference(path=path, key=key, uuid=value, entity_type="Unknown", hint="invalid UUID format")
        )
        result.add_error("Invalid UUID format", location=path, context={"key": key, "value": value})

    for path, key, value in valid_fields:
        entity_type = _resolve_uuid_target(path, key)
        if entity_type is None:
            details.unsupported.append(
                CloudReference(
                    path=path,
                    key=key,
                    uuid=value,
                    entity_type="Unknown",
                    hint="no cloud validator for this field",
                )
            )
            result.add_error(
                f"No cloud validator for field '{key}'",
                location=path,
                context={"uuid": value},
            )
            continue

        if entity_type == "Account":
            ok, error = _validate_current_account(hl_client, value)
            details.checks.append(
                {
                    "path": path,
                    "entity_type": entity_type,
                    "query": "currentAccount",
                    "arg": "uuid",
                    "uuid": value,
                    "ok": ok,
                    "error": error,
                }
            )
            if ok:
                details.validated.append(
                    CloudReference(path=path, key=key, uuid=value, entity_type=entity_type)
                )
            else:
                hint = "account does not match currentAccount"
                if error:
                    hint = f"lookup failed: {error}"
                result.add_error(
                    f"{entity_type} not found in cloud",
                    location=path,
                    context={"uuid": value, "error": error},
                )
                details.missing.append(
                    CloudReference(path=path, key=key, uuid=value, entity_type=entity_type, hint=hint)
                )
            continue

        if entity_type == "Attribute":
            ok, error = _validate_entity_attribute(hl_client, value)
            details.checks.append(
                {
                    "path": path,
                    "entity_type": entity_type,
                    "query": "entityAttribute",
                    "arg": "id",
                    "uuid": value,
                    "ok": ok,
                    "error": error,
                }
            )
            if ok:
                details.validated.append(
                    CloudReference(path=path, key=key, uuid=value, entity_type=entity_type)
                )
            else:
                hint = "attribute not found"
                if error:
                    hint = f"lookup failed: {error}"
                result.add_error(
                    f"{entity_type} not found in cloud",
                    location=path,
                    context={"uuid": value, "error": error},
                )
                details.missing.append(
                    CloudReference(path=path, key=key, uuid=value, entity_type=entity_type, hint=hint)
                )
            continue

        if entity_type == "AttributeEnum":
            ok, query_name, error = _validate_attribute_enum(hl_client, value, pipeline_def, path)
            details.checks.append(
                {
                    "path": path,
                    "entity_type": entity_type,
                    "query": query_name,
                    "arg": "uuid",
                    "uuid": value,
                    "ok": ok,
                    "error": error,
                }
            )
            if ok:
                details.validated.append(
                    CloudReference(path=path, key=key, uuid=value, entity_type=entity_type)
                )
            else:
                hint = "attribute enum not found"
                if error:
                    hint = f"lookup failed: {error}"
                result.add_error(
                    f"{entity_type} not found in cloud",
                    location=path,
                    context={"uuid": value, "error": error},
                )
                details.missing.append(
                    CloudReference(path=path, key=key, uuid=value, entity_type=entity_type, hint=hint)
                )
            continue

        candidates = _find_query_candidates(hl_client.schema, entity_type)
        if not candidates:
            result.add_error(
                f"No cloud query available for {entity_type}",
                location=path,
                context={"uuid": value},
            )
            details.unsupported.append(
                CloudReference(
                    path=path,
                    key=key,
                    uuid=value,
                    entity_type=entity_type,
                    hint="no query for entity type",
                )
            )
            continue

        last_error: Optional[str] = None
        exists = False
        for query_name, arg_name in candidates:
            try:
                exists = _execute_uuid_query(hl_client, query_name, arg_name, value)
                details.checks.append(
                    {
                        "path": path,
                        "entity_type": entity_type,
                        "query": query_name,
                        "arg": arg_name,
                        "uuid": value,
                        "ok": exists,
                    }
                )
                if exists:
                    break
            except Exception as e:
                last_error = str(e)
                details.checks.append(
                    {
                        "path": path,
                        "entity_type": entity_type,
                        "query": query_name,
                        "arg": arg_name,
                        "uuid": value,
                        "ok": False,
                        "error": last_error,
                    }
                )

        if last_error and not exists:
            result.add_error(
                f"Cloud lookup failed for {entity_type}",
                location=path,
                context={"uuid": value, "error": last_error},
            )
            details.missing.append(
                CloudReference(
                    path=path,
                    key=key,
                    uuid=value,
                    entity_type=entity_type,
                    hint=f"lookup failed: {last_error}",
                )
            )
            continue

        if not exists:
            result.add_error(
                f"{entity_type} not found in cloud",
                location=path,
                context={"uuid": value},
            )
            details.missing.append(
                CloudReference(path=path, key=key, uuid=value, entity_type=entity_type, hint="not found")
            )
        else:
            details.validated.append(CloudReference(path=path, key=key, uuid=value, entity_type=entity_type))

    return result, details
