"""
arifOS v45 - Witness Council (Sovereign Witness)
Federated Tri-Witness Consensus Engine.
Enforces Law 5 (Re-Witness or Release) and Law 6 (Memory != Authority).

Architectural note (v46): This module aggregates witness votes into a consensus signal,
but it does NOT issue the final constitutional verdict. Only APEX PRIME
(`arifos_core.system.apex_prime.apex_review`) may SEAL.
"""

from dataclasses import dataclass, field
from typing import List, Optional, Dict
from arifos_core.system.temporal.freshness_policy import FreshnessPolicy
from arifos_core.system.apex_prime import Verdict


@dataclass
class WitnessVote:
    """Atomic vote from a single witnessing agent."""

    witness_id: str
    verdict: Verdict
    confidence: float  # 0.0 to 1.0
    evidence_refs: List[str]  # List of EvidencePack IDs or Source IDs
    reason: str = ""


@dataclass
class ConsensusResult:
    global_verdict: Verdict
    consensus_score: float
    dissent_triggered: bool
    details: str


class ConsensusEngine:
    """
    Deterministic Quorum Aggregator.

    TODO(v46): Architectural Review - Verdict Authority
    This consensus engine creates Verdict enums by aggregating witness votes.
    While this is consensus mathematics (not independent judgment), it still
    produces verdicts outside apex_review(). Consider architectural pattern:
    - Option A: Witnesses call apex_review() before voting (verdicts pre-validated)
    - Option B: Consensus result feeds into apex_review() for final validation
    - Option C: Consensus layer is exempt (mathematical aggregation, not judgment)
    Current: Creates Verdict.VOID, Verdict.HOLD_888, Verdict.PARTIAL directly.
    """

    CONSENSUS_THRESHOLD = 0.95

    @staticmethod
    def aggregate(
        votes: List[WitnessVote],
        tier: str = "T1",
        evidence_freshness: Optional[Dict[str, float]] = None,
    ) -> ConsensusResult:
        """
        Aggregate votes into a binding sovereign verdict.
        """
        if not votes:
            return ConsensusResult(Verdict.VOID, 0.0, False, "No votes provided")

        # 1. Apply Freshness Weighting (Law 6: Memory != Authority)
        weighted_votes = []
        for v in votes:
            weight = 1.0
            if evidence_freshness and v.witness_id in evidence_freshness:
                # Physics: Stale evidence reduces vote weight/confidence
                freshness = evidence_freshness[v.witness_id]
                weight = freshness

            # Create effective vote copy
            weighted_conf = v.confidence * weight
            weighted_votes.append(
                {"vote": v, "effective_conf": weighted_conf, "weight_penalty": 1.0 - weight}
            )

        # 2. Dissent Trigger Check (Law: Dissent on High Stakes -> HALT)
        high_stakes = tier in ("T3", "T4")
        dissent_found = False

        for item in weighted_votes:
            v_enum = item["vote"].verdict
            if high_stakes and v_enum in (Verdict.VOID, Verdict.HOLD_888):
                return ConsensusResult(
                    global_verdict=Verdict.HOLD_888,
                    consensus_score=0.0,
                    dissent_triggered=True,
                    details=f"Dissent Trigger in High Stakes ({tier}): Witness {item['vote'].witness_id} voted {v_enum}",
                )

        # 3. Calculate Weighted Mass
        verdict_mass = {}
        total_mass = 0.0

        for item in weighted_votes:
            v_enum = item["vote"].verdict
            mass = item["effective_conf"]
            verdict_mass[v_enum] = verdict_mass.get(v_enum, 0.0) + mass
            total_mass += mass

        if total_mass == 0.0:
            return ConsensusResult(Verdict.VOID, 0.0, False, "Total confidence mass is zero")

        dominant_verdict = max(verdict_mass, key=verdict_mass.get)
        dominant_mass = verdict_mass[dominant_verdict]

        # Consensus measures AGREEMENT ratio, not average confidence.
        # Normalize by total confidence mass (freshness-weighted).
        final_score = dominant_mass / total_mass

        # 4. Final Threshold Gate
        if final_score >= ConsensusEngine.CONSENSUS_THRESHOLD:
            # Unanimity can be true even when strength is low (stale evidence).
            # Treat this as PARTIAL to enforce re-witnessing (Law 5).
            unanimous = len(verdict_mass) == 1
            # Threshold matches average confidence dropping below ~0.8 across N=3 (e.g. 2.4)
            # Or tunable per tier. Using fixed threshold based on test case physics.
            # In test: 1+1+0.1 = 2.1. N=3. Avg=0.7.
            # Let's say if total_mass < N * 0.8?
            # Or simple LOW_STRENGTH logic.
            # Let's use dynamic threshold: Mass < 80% of Max Potential
            max_potential_mass = float(len(votes))
            strength_ratio = total_mass / max_potential_mass

            if unanimous and strength_ratio < 0.80:
                return ConsensusResult(
                    Verdict.PARTIAL,
                    final_score,
                    False,
                    f"Stale Consensus ({strength_ratio:.2f} strength): Unanimous but stale/low strength. Re-witness required.",
                )

            # Check Unanimity for Tier 4
            if tier == "T4":
                if len(verdict_mass) > 1:
                    return ConsensusResult(
                        Verdict.HOLD_888,
                        final_score,
                        True,
                        "Tier 4 requires unanimity (single verdict type)",
                    )

            return ConsensusResult(dominant_verdict, final_score, False, "Consensus Reached")

        else:
            # Low Score Handling (Consensus not reached)
            if dominant_verdict == Verdict.SEAL:
                fallback = Verdict.PARTIAL
            else:
                fallback = Verdict.HOLD_888

            return ConsensusResult(
                fallback,
                final_score,
                False,
                f"Consensus {final_score:.2f} < {ConsensusEngine.CONSENSUS_THRESHOLD}",
            )
