"""
core/merge/swarm_result.py

SwarmResult — data model for a single worker's output in a parallel swarm run.
SwarmConflictDetector — identifies when 2+ workers produce contradictory outputs
for the same session.

Note: This module is Track A of the AIVA RLM Nexus PRD v2.
The name ``SwarmConflictDetector`` is intentionally distinct from
``ConflictDetector`` in core/merge/conflict_detector.py (Track B), which
operates on RFC 6902 StateDelta patches. This class operates on SwarmResult
output dicts instead.

# VERIFICATION_STAMP
# Story: 6.01
# Verified By: parallel-builder
# Verified At: 2026-02-25
# Tests: 29/29
# Coverage: 100%
"""

from __future__ import annotations

import logging
from dataclasses import dataclass
from datetime import datetime
from typing import Any

logger = logging.getLogger(__name__)


# ---------------------------------------------------------------------------
# Data model
# ---------------------------------------------------------------------------


@dataclass
class SwarmResult:
    """
    Represents the output produced by a single swarm worker for a given session.

    Attributes:
        session_id:    Identifier of the conversation/session this result belongs to.
        worker_name:   Name/ID of the worker that produced the result.
        output:        Free-form dict of key→value pairs produced by the worker.
        completed_at:  UTC timestamp when the worker finished.
        confidence:    Confidence score in [0.0, 1.0] for the result.
    """

    session_id: str
    worker_name: str
    output: dict
    completed_at: datetime
    confidence: float


# ---------------------------------------------------------------------------
# Conflict detector
# ---------------------------------------------------------------------------


class SwarmConflictDetector:
    """
    Identifies when 2+ SwarmResult objects for the same session contain
    contradictory output values.

    Conflict definition
    -------------------
    Two results conflict when they share the same session_id AND have at
    least one common output key whose values are:
        - both non-None, AND
        - not equal to each other.

    Keys present in only one result, or where either value is None, are
    considered complementary and NOT conflicts.
    """

    # ------------------------------------------------------------------
    # Public API
    # ------------------------------------------------------------------

    def detect(self, results: list[SwarmResult]) -> bool:
        """
        Return True if 2+ results for the same session have conflicting outputs.

        A conflict requires:
        - Same ``session_id``
        - Same output key
        - Both values non-None
        - Values are not equal

        Args:
            results: List of SwarmResult objects (may span multiple sessions).

        Returns:
            True if at least one conflict exists, False otherwise.
        """
        return bool(self.get_conflicts(results))

    def get_conflicts(self, results: list[SwarmResult]) -> list[tuple]:
        """
        Return all conflicting (result_a, result_b, conflicting_key) tuples.

        For every pair of results sharing the same session_id, each output key
        that appears in both with differing non-None values yields one tuple.
        Each conflicting key generates exactly one tuple per conflicting pair.

        Args:
            results: List of SwarmResult objects to inspect.

        Returns:
            List of (SwarmResult, SwarmResult, str) tuples — one per conflict.
            Empty list when no conflicts exist.
        """
        if len(results) < 2:
            return []

        conflicts: list[tuple] = []

        # Group by session_id so we only compare within the same session.
        by_session: dict[str, list[SwarmResult]] = {}
        for r in results:
            by_session.setdefault(r.session_id, []).append(r)

        for session_id, session_results in by_session.items():
            if len(session_results) < 2:
                continue

            # Compare every ordered pair (i, j) with i < j to avoid
            # producing duplicate (a,b) and (b,a) tuples for the same pair.
            for i in range(len(session_results)):
                for j in range(i + 1, len(session_results)):
                    result_a = session_results[i]
                    result_b = session_results[j]
                    self._find_pair_conflicts(result_a, result_b, conflicts)

        logger.debug(
            "SwarmConflictDetector.get_conflicts: %d results → %d conflicts",
            len(results),
            len(conflicts),
        )
        return conflicts

    # ------------------------------------------------------------------
    # Internal helpers
    # ------------------------------------------------------------------

    @staticmethod
    def _find_pair_conflicts(
        result_a: SwarmResult,
        result_b: SwarmResult,
        conflicts: list[tuple],
    ) -> None:
        """
        Append (result_a, result_b, key) to *conflicts* for every key that
        is present in both outputs with differing non-None values.

        Mutates *conflicts* in place.
        """
        output_a: dict[str, Any] = result_a.output
        output_b: dict[str, Any] = result_b.output

        # Only inspect keys that exist in BOTH outputs.
        shared_keys = output_a.keys() & output_b.keys()

        for key in sorted(shared_keys):  # sorted for deterministic ordering
            val_a = output_a[key]
            val_b = output_b[key]

            # None values are never treated as conflicts (complementary data).
            if val_a is None or val_b is None:
                continue

            # Different non-None values = conflict.
            if val_a != val_b:
                conflicts.append((result_a, result_b, key))
