"""CanonicalScannerGateBridge — default **Governor** bridge (canonical decision pipeline).

Architecture
------------
The canonical pipeline is:

  flags + mode + context
    -> StatusTranslator   -> LensStatus
    -> ContextResolver    -> (Domain, AuthorityClass, UserClass, Provenance)
    -> PolicyResolver     -> GovernorPolicy
    -> PolicyProjector    -> RuntimeDecisionProjection
    -> bridge plumbing    -> GovernanceDecision + forensic envelope + attestation

InterventionPolicy is NOT called from decide(). PolicyResolver is the sole
decision authority. This eliminates split authority — the condition under which
governance systems start "lying politely" by allowing two policy brains to
produce incompatible verdicts and resolving the conflict silently.

This bridge extends AuroraScannerGateBridge solely to reuse its ledger, CID
provider, and HMAC signing infrastructure. The decide() method is completely
replaced. The log_decision() method enriches the audit payload with canonical
Governor metadata via aurora_lens.governor.audit.enrich_forensic_envelope().

All infrastructure is local — no external dependencies required.
"""

from __future__ import annotations

import contextvars
import datetime
from pathlib import Path
from typing import TYPE_CHECKING, Any

from aurora_lens.govern.scanner_gate_bridge import AuroraScannerGateBridge
from aurora_lens.govern.adapters.status_translator import StatusTranslator
from aurora_lens.govern.adapters.context_resolver import ContextResolver
from aurora_lens.govern.adapters.policy_projector import PolicyProjector
from aurora_lens.govern.adapters.runtime_types import RuntimeDecisionProjection
from aurora_lens.govern.decision import GovernanceDecision, InterventionAction
from aurora_lens.govern.bridge import ESCALATION_ROUTES, build_forensic_event, enforce
from aurora_lens.verify.flags import Flag, FlagType

from aurora_lens.governor.resolver import PolicyResolver
from aurora_lens.governor.audit import enrich_forensic_envelope

if TYPE_CHECKING:
    from aurora_lens.adapters.base import LLMAdapter
    from aurora_lens.pef.state import PEFState


# Safety-veto flags: canonical HARD_STOP cannot be softened for any per-key
# policy, including "moderate". These flags represent immediate physical harm,
# illegal acts, or forensic obligations that exist independently of commercial
# policy preference. The Governor's HARD_STOP on these is always final.
_SAFETY_VETO_FLAGS: frozenset[FlagType] = frozenset({
    FlagType.SELF_HARM_INSTRUCTION,
    FlagType.ILLEGAL_INSTRUCTION,
    FlagType.MEDICAL_DOSAGE_RECOMMENDATION,
    FlagType.PEDIATRIC_DOSAGE_RECOMMENDATION,
    FlagType.NUMERIC_MEDICAL_INSTRUCTION,
    FlagType.EMERGENCY_TRIAGE_GUIDANCE,
})


# Per-async-context storage for the GovernorPolicy resolved in decide().
# This is read by log_decision() to enrich the forensic payload.
# Using ContextVar ensures async-safe isolation between concurrent requests.
_scanner_gate_policy_var: contextvars.ContextVar[Any] = contextvars.ContextVar(
    "_aurora_scanner_gate_policy",
    default=None,
)
_scanner_gate_projection_var: contextvars.ContextVar[RuntimeDecisionProjection | None] = (
    contextvars.ContextVar("_aurora_scanner_gate_projection", default=None)
)


class CanonicalScannerGateBridge(AuroraScannerGateBridge):
    """Governance bridge using the canonical Governor pipeline as sole decision authority.

    PolicyResolver replaces InterventionPolicy for all admissibility decisions.
    GovernanceKernel is retained for signing, CID generation, and ledger append
    only — kernel_step() is never called.

    The public interface (decide, intervene, log_decision, verify_ledger,
    verify_attestation, last_attestation, kernel_stats, ledger_stats) is
    identical to AuroraScannerGateBridge. Existing callers work without changes.

    Additional introspection:
        bridge.last_scanner_gate_policy — the GovernorPolicy resolved on the
                                          last call to decide() (for testing)
        bridge.last_projection          — the RuntimeDecisionProjection from the
                                          last call to decide()
    """

    def __init__(
        self,
        mode: str = "public",
        audit_path: str | Path | None = None,
        secret_key: bytes | None = None,
        constraints: dict | None = None,
        max_revision_attempts: int = 1,
        backend: str = "ledger",
        default_policy: str = "strict",
        policy_version: str = "1.0",
        policy_matrix_path: str | Path | None = None,
    ):
        """
        Args:
            mode:                  Deployment policy mode ("public" | "enterprise" | "open").
                                   Used by StatusTranslator for mode-sensitive flags.
            audit_path:            Path for audit log.
            secret_key:            HMAC key for AttestedOutput signing.
            constraints:           Kernel constraints (passed to substrate; not used for policy).
            max_revision_attempts: Hard limit on revision loop (1-10).
            backend:               Audit format — "ledger" (AFL-JSONL-1) or "jsonl" (flat JSONL).
            default_policy:        Deployment default policy name for flat JSONL policy_profile.
        """
        # AuroraScannerGateBridge.__init__ sets up ledger, CID provider, attestation.
        # We pass policy=None; the parent stores DEFAULT_STRICT but it is never
        # called from our override of decide().
        super().__init__(
            policy=None,
            audit_path=audit_path,
            secret_key=secret_key,
            constraints=constraints or {},
            max_revision_attempts=max_revision_attempts,
            backend=backend,
            default_policy=default_policy,
        )

        if mode not in ("public", "enterprise", "open"):
            raise ValueError(
                f"Unknown mode: {mode!r} — expected 'public', 'enterprise', or 'open'"
            )
        self._mode = mode

        # Canonical Governor pipeline components.
        self._status_translator = StatusTranslator()
        self._context_resolver = ContextResolver()
        self._policy_projector = PolicyProjector()
        self._policy_resolver = PolicyResolver(
            matrix_path=Path(policy_matrix_path) if policy_matrix_path else None,
            policy_version=policy_version,
        )
        self._policy_version = policy_version

        # Introspection (last call; useful for testing).
        self._last_scanner_gate_policy: Any = None
        self._last_projection: RuntimeDecisionProjection | None = None

    # -- GovernanceBridge.decide() — canonical Governor pipeline -------------

    async def decide(
        self,
        flags: list[Flag],
        response_text: str,
        pef: "PEFState",
    ) -> GovernanceDecision:
        """Evaluate flags through the canonical Governor pipeline.

        Pipeline:
          1. StatusTranslator  — flags + mode -> LensStatus
          2. ContextResolver   — flags + context vars -> (Domain, AC, UC, Provenance)
          3. PolicyResolver    — (Domain, AC, LensStatus, UC) -> GovernorPolicy
          4. PolicyProjector   — GovernorPolicy -> RuntimeDecisionProjection
          5. Build GovernanceDecision from projection.
             Canonical pathway fields are copied directly; no reinterpretation.

        GovernanceKernel.kernel_step() is NOT called here.
        InterventionPolicy.evaluate_with_rule() is NOT called here.
        Per-key "moderate" softening is a post-projection corridor check only;
        it never calls the legacy policy engine.
        """
        # 1. Terminal LensStatus from flags + mode.
        lens_status = self._status_translator.translate(flags, self._mode)

        # 2. Governance corridor context.
        domain, authority, user_class, provenance = self._context_resolver.resolve(flags)

        # 3. Canonical Governor decision.
        # reason_code is the primary flag type name, used by the resolver's level-0
        # flag-class key lookup (domain:authority:status:reason_code). This lets flags
        # that share a corridor — e.g. SELF_HARM_INSTRUCTION and EMERGENCY_TRIAGE_GUIDANCE
        # both in medical:GP:STOP — resolve to distinct pathways without splitting domains.
        reason_code = flags[0].flag_type.name if flags else None
        policy = self._policy_resolver.resolve(
            domain=domain,
            authority=authority,
            status=lens_status,
            user_class=user_class,
            reason_code=reason_code,
        )

        # 4. Project to runtime decision record.
        projection = self._policy_projector.project(policy, provenance)

        # Store for log_decision() and introspection — async-safe via ContextVar.
        _scanner_gate_policy_var.set(policy)
        _scanner_gate_projection_var.set(projection)
        self._last_scanner_gate_policy = policy
        self._last_projection = projection

        # 5. Build GovernanceDecision carrying canonical pathway metadata.
        action = projection.intervention_action
        cid = None  # CID assigned in log_decision() when ledger is present.

        # Per-key policy override: "moderate" key may soften a canonical HARD_STOP
        # to FORCE_REVISE, but only for non-safety-critical flags.
        # Safety-veto flags (_SAFETY_VETO_FLAGS) are always final regardless of key policy.
        # No InterventionPolicy call — the canonical Governor is the sole authority.
        from aurora_lens.context import auth_policy_var as _auth_policy_var
        _per_key_policy = _auth_policy_var.get(None)
        _softened_pathway_id: str | None = None
        if (
            _per_key_policy == "moderate"
            and action in (InterventionAction.FORCE_REVISE, InterventionAction.HARD_STOP)
            and flags
            and not {f.flag_type for f in flags} & _SAFETY_VETO_FLAGS
        ):
            # SOFT_CORRECT: deliver model output with governance annotation.
            # The canonical pathway_id is kept for audit (it shows what the Governor
            # decided before the override, which is the forensically relevant fact).
            action = InterventionAction.SOFT_CORRECT
            _softened_pathway_id = None

        rationale = self._build_rationale(flags, action)
        note = (
            self._build_governance_note(flags)
            if action == InterventionAction.SOFT_CORRECT
            else None
        )

        # Resource routing: use escalation_target from Governor; fall back to
        # ESCALATION_ROUTES for backward compatibility.
        resource = projection.escalation_target
        if resource is None and flags:
            route = ESCALATION_ROUTES.get(
                flags[0].flag_type.name, (None, None, None, None)
            )
            resource = route[3] if len(route) > 3 else None

        # rule_id is not available from the canonical Governor (it resolves by
        # domain:authority:status:user_class, not per-flag rules). Leave None.
        #
        # policy: use effective per-key policy name when set, else deployment default.
        # This keeps the field human-readable ("strict" | "moderate") consistent with
        # BuiltinBridge and the flat JSONL audit format.
        _effective_policy = _per_key_policy or self._default_policy

        decision = GovernanceDecision(
            action=action,
            flags=flags,
            rationale=rationale,
            policy=_effective_policy,
            attempt=0,
            governance_note=note,
            cid=cid,
            rule_id=None,
            safe_alt=None,
            resource=resource,
        )

        # Copy canonical continuation fields directly from the projection.
        # When a moderate-key softening occurred, _softened_pathway_id overrides
        # the canonical pathway so that action and pathway_id stay coherent in audit.
        decision.pathway_id = _softened_pathway_id or projection.pathway_id.value
        decision.output_mode = projection.output_mode.value
        decision.commitment_closed = projection.commitment_closed
        decision.interaction_open = projection.interaction_open
        decision.forensic_obligations = [fo.value for fo in projection.forensic_obligations]
        decision.resolution_mode = projection.resolution_mode.value

        return decision

    # -- log_decision() — enriched with canonical Governor fields ------------

    def log_decision(
        self,
        decision: GovernanceDecision,
        turn: int = 0,
        *,
        stream: bool = False,
        stream_completed: bool = True,
        stream_abort_reason: str | None = None,
        stream_truncated: bool = False,
        stream_dropped_chars: int = 0,
        pef_context: str | None = None,
        pre_llm: bool = False,
        pef_snapshot: dict | None = None,
    ) -> None:
        """Log to ForensicLedger + AttestedOutput, enriched with Governor fields.

        Calls the parent log_decision() and then enriches the stored forensic
        event with canonical Governor metadata via governor.audit.
        """
        super().log_decision(
            decision,
            turn=turn,
            stream=stream,
            stream_completed=stream_completed,
            stream_abort_reason=stream_abort_reason,
            stream_truncated=stream_truncated,
            stream_dropped_chars=stream_dropped_chars,
            pef_context=pef_context,
            pre_llm=pre_llm,
            pef_snapshot=pef_snapshot,
        )

        # Enrich forensic event with canonical Governor fields, if one was built.
        policy = _scanner_gate_policy_var.get(None)
        projection = _scanner_gate_projection_var.get(None)
        if policy is not None and decision.forensic_event is not None:
            provenance_dict = None
            if projection is not None and projection.provenance is not None:
                p = projection.provenance
                provenance_dict = {
                    "domain_source": p.domain_source,
                    "authority_source": p.authority_source,
                    "user_class_source": p.user_class_source,
                }

            reason_code = decision.flags[0].flag_type.name if decision.flags else "none"
            enriched = enrich_forensic_envelope(
                decision.forensic_event,
                policy,
                reason_code,
            )
            if provenance_dict is not None:
                enriched["context_provenance"] = provenance_dict

            # Surface pathway metadata in the forensic envelope.
            enriched["pathway_id"] = decision.pathway_id
            enriched["output_mode"] = decision.output_mode
            enriched["commitment_closed"] = decision.commitment_closed
            enriched["interaction_open"] = decision.interaction_open
            enriched["forensic_obligations"] = decision.forensic_obligations
            enriched["resolution_mode"] = decision.resolution_mode
            enriched["rendered_from_policy"] = True
            # Policy versioning and source provenance.
            enriched["policy_version"] = self._policy_resolver.active_version
            enriched["policy_source"] = self._policy_resolver.policy_source

            decision.forensic_event = enriched

    # -- Introspection ----------------------------------------------------------

    @property
    def last_scanner_gate_policy(self) -> Any:
        """The GovernorPolicy resolved on the last call to decide(). For testing."""
        return self._last_scanner_gate_policy

    @property
    def last_projection(self) -> RuntimeDecisionProjection | None:
        """The RuntimeDecisionProjection from the last call to decide(). For testing."""
        return self._last_projection
