"""
Peer Query Protocol — Geometric Inter-Kernel Knowledge Routing

Structural Leg L3: Without peer query, kernels are islands. Multi-kernel
generation is parallel monologue, not collaboration. "Ask a peer who
knows" requires geometric routing.

Mechanism:
  - A kernel needing knowledge it doesn't have emits a PeerQuery
  - The router finds the kernel(s) whose basin position is closest
    to the query basin (FR proximity = expertise proximity)
  - Each candidate kernel searches its own knowledge for the best
    match and returns a PeerResponse with confidence
  - Escalation: if no peer has confidence > threshold, the query
    escalates to constellation or external search

Purity: Fisher-Rao metric only. No text matching, no Euclidean ops.
"""

from __future__ import annotations

import logging
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Any

from ..coordizer.geometry import Basin, fisher_rao_distance, to_simplex

if TYPE_CHECKING:
    from .topology import KernelTopology

logger = logging.getLogger(__name__)

# Confidence below this → escalate to next level
PEER_CONFIDENCE_THRESHOLD: float = 0.3

# Maximum peers to query per request
MAX_PEERS_PER_QUERY: int = 3


@dataclass
class PeerQuery:
    """A geometric query from one kernel to the constellation."""

    query_basin: Basin
    """What I'm looking for (point on Δ⁶³)."""

    requesting_kernel_id: str
    """Who's asking."""

    urgency: float = 0.5
    """0-1: how badly the kernel needs this. Higher = wider search radius."""

    context: dict[str, Any] = field(default_factory=dict)
    """Optional metadata (e.g., modality hint, domain tag)."""


@dataclass
class PeerResponse:
    """A geometric response from a peer kernel."""

    responding_kernel_id: str
    """Who answered."""

    relevance: float
    """FR distance from query to responder's basin (lower = more relevant)."""

    content_basin: Basin
    """The knowledge point closest to the query from responder's bank."""

    confidence: float
    """0-1: how well the responder actually knows this.
    Derived from FR distance between query and best match in their bank."""

    @property
    def should_escalate(self) -> bool:
        """True if this response is too weak and should escalate."""
        return self.confidence < PEER_CONFIDENCE_THRESHOLD


@dataclass
class QueryResult:
    """Aggregated result of a peer query."""

    responses: list[PeerResponse]
    """All peer responses, sorted by confidence descending."""

    best: PeerResponse | None
    """Best response (highest confidence), or None if no peers available."""

    should_escalate: bool
    """True if best response is below threshold → try constellation/external."""

    source: str = "peer"
    """Resolution level: 'self', 'peer', 'constellation', 'external'."""


class PeerQueryRouter:
    """Routes geometric queries to the most relevant peer kernels.

    A kernel's basin position IS its expertise — the router finds
    the closest kernels by FR distance and asks them for their best
    knowledge match.
    """

    def __init__(self, max_peers: int = MAX_PEERS_PER_QUERY) -> None:
        self._max_peers = max_peers

    def route(
        self,
        query: PeerQuery,
        kernel_basins: dict[str, Basin],
        kernel_banks: dict[str, dict[int, Basin]] | None = None,
        topology: KernelTopology | None = None,
    ) -> QueryResult:
        """Route a query to the most relevant peer kernels.

        EXP-002: When topology is provided, interior kernels get a
        confidence boost reflecting their ~67× better geometric integrity.
        This makes queries prefer routing through interior nodes.

        Args:
            query: The geometric query.
            kernel_basins: kernel_id → current basin position (expertise location).
            kernel_banks: Optional kernel_id → {token_id: basin} for detailed
                knowledge search. If None, confidence is estimated from
                basin proximity alone.
            topology: Optional kernel topology for EXP-002 bulk protection.

        Returns:
            QueryResult with sorted responses and escalation recommendation.
        """
        query_basin = to_simplex(query.query_basin)

        # Rank all kernels by FR distance to query (closest = most likely to know)
        candidates: list[tuple[str, float]] = []
        for kid, basin in kernel_basins.items():
            if kid == query.requesting_kernel_id:
                continue  # don't ask yourself
            dist = fisher_rao_distance(query_basin, to_simplex(basin))
            candidates.append((kid, float(dist)))

        # Sort by distance (ascending = most relevant first)
        candidates.sort(key=lambda x: x[1])

        # Urgency widens search: high urgency → query more peers
        n_peers = min(
            max(1, int(self._max_peers * (0.5 + query.urgency))),
            len(candidates),
        )

        responses: list[PeerResponse] = []
        for kid, dist in candidates[:n_peers]:
            # Compute confidence from the peer's perspective
            confidence = self._compute_peer_confidence(
                query_basin=query_basin,
                peer_id=kid,
                peer_basin=kernel_basins[kid],
                peer_bank=kernel_banks.get(kid) if kernel_banks else None,
            )

            # Find the closest knowledge point in peer's bank (if available)
            content_basin = self._find_closest_knowledge(
                query_basin=query_basin,
                peer_bank=kernel_banks.get(kid) if kernel_banks else None,
                peer_basin=kernel_basins[kid],
            )

            # EXP-002: Interior kernels get confidence boost
            if topology is not None:
                confidence *= topology.bulk_protection_weight(kid)
                confidence = min(confidence, 1.0)  # cap at 1.0

            responses.append(
                PeerResponse(
                    responding_kernel_id=kid,
                    relevance=dist,
                    content_basin=content_basin,
                    confidence=confidence,
                )
            )

        # Sort by confidence descending
        responses.sort(key=lambda r: r.confidence, reverse=True)

        best = responses[0] if responses else None
        should_escalate = best is None or best.should_escalate

        if should_escalate:
            logger.debug(
                "Peer query from %s: escalating (best confidence=%.3f)",
                query.requesting_kernel_id,
                best.confidence if best else 0.0,
            )

        return QueryResult(
            responses=responses,
            best=best,
            should_escalate=should_escalate,
        )

    def _compute_peer_confidence(
        self,
        query_basin: Basin,
        peer_id: str,
        peer_basin: Basin,
        peer_bank: dict[int, Basin] | None,
    ) -> float:
        """Compute how confident a peer is about answering this query.

        If the peer has a bank, confidence = 1 / (1 + min_distance_to_query).
        Otherwise, confidence is estimated from basin proximity.
        """
        if peer_bank:
            # Search peer's bank for closest coordinate to query
            min_dist = float("inf")
            for basin in peer_bank.values():
                dist = fisher_rao_distance(query_basin, to_simplex(basin))
                min_dist = min(min_dist, dist)
            # Transform distance to confidence: close → high confidence
            return float(1.0 / (1.0 + min_dist))

        # No bank available — estimate from basin proximity alone
        dist = fisher_rao_distance(query_basin, to_simplex(peer_basin))
        return float(1.0 / (1.0 + dist * 2.0))  # weaker confidence without bank

    def _find_closest_knowledge(
        self,
        query_basin: Basin,
        peer_bank: dict[int, Basin] | None,
        peer_basin: Basin,
    ) -> Basin:
        """Find the closest knowledge point in the peer's bank.

        Falls back to the peer's own basin if no bank available.
        """
        if not peer_bank:
            return to_simplex(peer_basin)

        best_basin = to_simplex(peer_basin)
        best_dist = float("inf")
        for basin in peer_bank.values():
            basin_s = to_simplex(basin)
            dist = fisher_rao_distance(query_basin, basin_s)
            if dist < best_dist:
                best_dist = dist
                best_basin = basin_s

        return best_basin
