"""Normalize the WorkIQ retrieval artifact into ``retrieval_executions[]``.

Raw shape (from the ``application/vnd.ms-workiq-internal.retrieval`` artifact):

```
artifact.parts[0].data.executions[] = [
    {
        "name": "OneDriveAndSharePoint",
        "status": "success",
        "debugInfo": {
            "queryString": "...",
            "filterExpression": "...",
            "maximumNumberOfResults": 50,
            "totalResultCount": 13,
            "latencyMs": 412,
            "retrievalHits": [
                {
                    "webUrl": "...",
                    "rank": 1,
                    "extracts": [{"text": "..."}],
                    "resourceMetadata": {...}
                }
            ]
        }
    }
]
```

The provider wraps the flat ``debugInfo`` scalars into a single-element
``queries[]`` array and lifts execution-level fields up so evaluators see a
uniform shape. See ``specs/WI-6855067-retrieval-evaluators/data-model.md``
"Raw Artifact Mapping" for the authoritative mapping.
"""

from __future__ import annotations

from typing import Any, Dict, List, Optional


class RetrievalArtifactProvider:
    """Adapter that converts the raw retrieval artifact into evaluator input.

    Stateless — call :meth:`normalize` per A2A response. The provider does
    NOT consume URL fields for matching; ``webUrl`` is passed through to the
    normalized model unchanged (V2 uses it as a join key / V1 uses it only
    for diagnostic ``matchedHitUrl`` output).
    """

    @staticmethod
    def normalize(retrieval_info: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
        """Convert the raw artifact data dict into normalized telemetry.

        ``retrieval_info`` is the ``artifact.parts[0].data`` payload extracted
        by the A2A client (see ``a2a_client._extract_artifact_part_data``).

        Returns ``None`` when the input is missing or not a dict. Returns the
        normalized telemetry dict otherwise, with at minimum a
        ``retrieval_executions[]`` array (possibly empty) plus a
        ``raw_artifact`` passthrough for diagnostics.
        """
        if not retrieval_info or not isinstance(retrieval_info, dict):
            return None

        raw_executions = retrieval_info.get("executions")
        if not isinstance(raw_executions, list):
            raw_executions = []

        normalized_executions: List[Dict[str, Any]] = []
        for raw in raw_executions:
            if not isinstance(raw, dict):
                continue
            normalized = RetrievalArtifactProvider._normalize_execution(raw)
            if normalized is not None:
                normalized_executions.append(normalized)

        return {
            "source": "workiq-retrieval-artifact",
            "retrieval_executions": normalized_executions,
            "raw_artifact": retrieval_info,
        }

    @staticmethod
    def _normalize_execution(raw: Dict[str, Any]) -> Optional[Dict[str, Any]]:
        """Convert one raw execution record into the normalized shape.

        Returns ``None`` if the record has no ``name`` (capability), which
        means it isn't a retrieval-oriented execution we can scope to.
        """
        capability = raw.get("name")
        if not isinstance(capability, str) or not capability:
            return None

        debug_info = raw.get("debugInfo") or {}
        if not isinstance(debug_info, dict):
            debug_info = {}

        status = raw.get("status")
        if isinstance(status, str):
            status_norm = status.lower()
        else:
            status_norm = "success"

        query_string = debug_info.get("queryString")
        if not isinstance(query_string, str):
            query_string = ""
        filter_expression = debug_info.get("filterExpression")
        if not isinstance(filter_expression, str):
            filter_expression = None
        max_num_results = debug_info.get("maximumNumberOfResults")
        if not isinstance(max_num_results, int):
            max_num_results = None

        queries: List[Dict[str, Any]] = [
            {
                "queryString": query_string,
                "filterExpression": filter_expression,
                "maximumNumberOfResults": max_num_results,
            }
        ]

        latency_ms = debug_info.get("latencyMs")
        if not isinstance(latency_ms, (int, float)):
            latency_ms = None
        total_result_count = debug_info.get("totalResultCount")
        if not isinstance(total_result_count, int):
            total_result_count = None

        raw_hits = debug_info.get("retrievalHits") or []
        retrieval_hits: List[Dict[str, Any]] = []
        if isinstance(raw_hits, list):
            for hit in raw_hits:
                normalized_hit = RetrievalArtifactProvider._normalize_hit(hit)
                if normalized_hit is not None:
                    retrieval_hits.append(normalized_hit)

        return {
            "capability": capability,
            "status": status_norm,
            "queries": queries,
            "latencyMs": latency_ms,
            "totalResultCount": total_result_count,
            "retrievalHits": retrieval_hits,
            "raw_debugInfo": debug_info,
        }

    @staticmethod
    def _normalize_hit(hit: Any) -> Optional[Dict[str, Any]]:
        """Pass through a retrieval hit, normalizing extract structure."""
        if not isinstance(hit, dict):
            return None
        web_url = hit.get("webUrl")
        if not isinstance(web_url, str):
            web_url = ""
        rank = hit.get("rank")
        if not isinstance(rank, int):
            return None  # rank is load-bearing for max_rank filtering

        raw_extracts = hit.get("extracts") or []
        extracts: List[Dict[str, Any]] = []
        if isinstance(raw_extracts, list):
            for extract in raw_extracts:
                if not isinstance(extract, dict):
                    continue
                text = extract.get("text")
                if not isinstance(text, str):
                    continue
                extracts.append({"text": text})

        resource_metadata = hit.get("resourceMetadata")
        if not isinstance(resource_metadata, dict):
            resource_metadata = None

        return {
            "webUrl": web_url,
            "rank": rank,
            "extracts": extracts,
            "resourceMetadata": resource_metadata,
        }
