"""A + §1.4 mapping + §4 classifier — the FAIL-CLOSED bridge from an agent / tool-call event to a canonical VEB.

The bridge is the heart of the seam. It maps one agent telemetry event (the `AgentToolCallEvent` shape) into
either an ordinary VEB observation or a VEB ⊃ PAC-RO transaction, under TWO hard rules:

  §4 classifier — CONSEQUENTIAL iff `event_type` is declared in the connector profile's
  `consequential_event_types`; an UNMARKED event is an OBSERVATION (non-consequential) by default.

  §1.4 mapping (FAIL-CLOSED — §3: controlled consequential execution requires a verified ActionIntent AND a
  verified AuthorityDecision; either missing → EXCEPTION, NEVER an inferred FULFILLMENT, no authority bypass):
    * non-consequential                                         → ordinary VEB observation (no PAC-RO)
    * consequential + success + intent + authority-verified     → PAC-RO FULFILLMENT
    * consequential + success + intent + NOT authority-verified → PAC-RO EXCEPTION (MISSING_AUTHORITY_DECISION)
    * consequential + success + no intent                       → PAC-RO EXCEPTION (MISSING_ACTION_INTENT)
    * consequential + {error,denied,cancelled}                  → PAC-RO EXCEPTION (EXECUTION_FAILURE)

Transport correlation (trace_id / span_id / parent_span_id / session_id) goes to EVIDENCE / provenance only,
never into semantic `facts` — unless a profile flag makes it semantic. principal_ref / tenant come from the
verified context, never the body; `actor_ref` = `agent_id` ONLY if it matches the verified token's subject.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Optional

from cl.agent_seam.action_intent import ActionIntentV1
from cl.agent_seam.context import VerifiedContext, resolve_identity
from cl.model.pacro import PacRoBodyV1, pacro_inline_body
from cl.model.veb import InlineCanonicalBody, VebContentV1

# §4 classification
OBSERVATION = "OBSERVATION"
CONSEQUENTIAL = "CONSEQUENTIAL"

# statuses that route a consequential event straight to EXCEPTION
EXCEPTION_STATUSES = frozenset({"error", "denied", "cancelled"})

AGENT_OBSERVATION_SCHEMA = "AGENT_OBSERVATION_V1"
AGENT_OBSERVATION_EVENT_TYPE = "AGENT_OBSERVATION_RECORDED"
AGENT_ACTION_EVENT_TYPE = "AGENT_ACTION_RECORDED"


@dataclass(frozen=True)
class ConnectorProfile:
    """A connector profile declares which agent `event_type`s are controlled / consequential. Everything not
    declared is an OBSERVATION by default (§4). `transport_is_semantic` is the explicit, per-profile opt-in
    that would move transport correlation into `facts` (default OFF — transport stays in evidence)."""

    connector_id: str
    consequential_event_types: frozenset = field(default_factory=frozenset)
    transport_is_semantic: bool = False


def classify(event_type: str, profile: ConnectorProfile) -> str:
    """§4 — CONSEQUENTIAL iff `event_type` is declared consequential by the profile; else OBSERVATION."""
    return CONSEQUENTIAL if event_type in profile.consequential_event_types else OBSERVATION


def _transport_evidence(event: dict) -> dict:
    """The transport / provenance correlation fields — EVIDENCE only, never semantic facts."""
    return {
        "trace_id": event.get("trace_id"),
        "span_id": event.get("span_id"),
        "parent_span_id": event.get("parent_span_id"),
        "session_id": event.get("session_id"),
    }


def _resolve_actor(event: dict, ctx: VerifiedContext) -> str:
    """`actor_ref` = the body `agent_id` ONLY if it matches the verified token subject; otherwise fall back to
    the context identity (the token wins, never the body)."""
    actor_ref, _principal, _tenant = resolve_identity(ctx, event)
    agent_id = event.get("agent_id")
    if agent_id is not None and agent_id == ctx.sub:
        return agent_id
    return actor_ref


def agent_event_to_action_intent(
    event: dict,
    *,
    ctx: VerifiedContext,
    hash_profile_id: bytes,
    authority_context_manifest_hash: str,
    operation: Optional[str] = None,
    policy_snapshot_ref: Optional[str] = None,
    valid_until: Optional[str] = None,
) -> ActionIntentV1:
    """Map a PRE-EXECUTION agent event into an `ActionIntentV1`. principal_ref / tenant come from `ctx`; the
    input commitment is the event's `tool_input_hash`."""
    _actor, principal_ref, tenant = resolve_identity(ctx, event)
    return ActionIntentV1(
        schema_id="action_intent",
        schema_version=1,
        hash_profile_id=hash_profile_id,
        operation=operation or event.get("tool_name") or event.get("event_type") or "",
        actor_ref=_resolve_actor(event, ctx),
        principal_ref=principal_ref,
        tenant=tenant,
        input_commitment=event.get("tool_input_hash", ""),
        authority_context_manifest_hash=authority_context_manifest_hash,
        semantic_time=event.get("ts_start", ""),
        policy_snapshot_ref=policy_snapshot_ref,
        valid_until=valid_until,
    )


def _signature_policy(event: dict) -> dict:
    """The PAC-RO signature_policy element (non-empty, structural). The approval signal carries the
    human-oversight record (Art 14/26)."""
    approval = event.get("approval") or {}
    return {
        "approval_mode": approval.get("mode", "auto"),
        "approval_actor_ref": approval.get("actor_ref"),
    }


def _veb(
    event: dict,
    *,
    ctx: VerifiedContext,
    hash_profile_id: bytes,
    event_type: str,
    body_binding,
    evidence_refs: tuple = (),
) -> VebContentV1:
    _actor, principal_ref, _tenant = resolve_identity(ctx, event)
    return VebContentV1(
        schema_id="veb",
        schema_version=1,
        hash_profile_id=hash_profile_id,
        event_type=event_type,
        actor_ref=_resolve_actor(event, ctx),
        principal_ref=principal_ref,
        hai5_level=2,
        semantic_time=event.get("ts_start", ""),
        body_binding=body_binding,
        evidence_refs=evidence_refs,
    )


def _observation_veb(event: dict, *, ctx, hash_profile_id, profile) -> VebContentV1:
    transport = _transport_evidence(event)
    observation: dict = {
        "tool_name": event.get("tool_name"),
        "event_type": event.get("event_type"),
        "status": event.get("status"),
        "tool_input_hash": event.get("tool_input_hash"),
        "tool_output_hash": event.get("tool_output_hash"),
        "model_ref": event.get("model_ref"),
    }
    if profile.transport_is_semantic:
        observation.update({k: v for k, v in transport.items() if v is not None})
    body = InlineCanonicalBody(AGENT_OBSERVATION_SCHEMA, observation, non_personal=True)
    return _veb(
        event,
        ctx=ctx,
        hash_profile_id=hash_profile_id,
        event_type=AGENT_OBSERVATION_EVENT_TYPE,
        body_binding=body,
        evidence_refs=(),
    )


def _pacro_veb(
    event: dict,
    *,
    ctx,
    hash_profile_id,
    profile,
    event_class: str,
    facts: dict,
    exception_semantics: dict,
) -> VebContentV1:
    transport = _transport_evidence(event)
    evidence: dict = {
        "transport": {k: v for k, v in transport.items() if v is not None},
        "tool_output_hash": event.get("tool_output_hash"),
        "status": event.get("status"),
        "model_ref": event.get("model_ref"),
    }
    pacro = PacRoBodyV1(
        event_class=event_class,
        lifecycle_state="RECORDED",
        pac_bcvc_ref=event.get("trace_id", "urn:pac_bcvc:agent"),
        facts=facts,
        evidence=evidence,
        signature_policy=_signature_policy(event),
        exception_semantics=exception_semantics,
    )
    return _veb(
        event,
        ctx=ctx,
        hash_profile_id=hash_profile_id,
        event_type=AGENT_ACTION_EVENT_TYPE,
        body_binding=pacro_inline_body(pacro),
        evidence_refs=tuple(v for v in transport.values() if v is not None),
    )


def bridge_agent_event(
    event: dict,
    *,
    profile: ConnectorProfile,
    verified_ctx: VerifiedContext,
    hash_profile_id: bytes,
    action_intent_ref: Optional[str] = None,
    authority_decision_verified: bool = False,
) -> VebContentV1:
    """Map one agent event to a canonical VEB per §1.4 (FAIL-CLOSED). See module docstring for the table.

    `action_intent_ref` is the hex `action_intent_hash` of a pre-execution intent (when present). The bridge
    NEVER infers a FULFILLMENT: a consequential success without an intent ref becomes a MISSING_ACTION_INTENT
    EXCEPTION. `authority_decision_verified` is the verified-AuthorityDecision signal; a consequential event
    lacking both an intent AND a verified decision is forced to EXCEPTION (the fail-closed override).
    """
    classification = classify(event.get("event_type", ""), profile)

    # non-consequential → ordinary VEB observation (no PAC-RO)
    if classification != CONSEQUENTIAL:
        return _observation_veb(event, ctx=verified_ctx, hash_profile_id=hash_profile_id, profile=profile)

    status = event.get("status")
    facts_base = {
        "operation": event.get("tool_name"),
        "event_type": event.get("event_type"),
    }

    # failed / denied / cancelled → EXCEPTION (independent of intent / authority)
    if status in EXCEPTION_STATUSES:
        return _pacro_veb(
            event,
            ctx=verified_ctx,
            hash_profile_id=hash_profile_id,
            profile=profile,
            event_class="EXCEPTION",
            facts=facts_base,
            exception_semantics={
                "exception_type": "EXECUTION_FAILURE",
                "status": status,
                "fail_closed": True,
            },
        )

    # SUCCESS PATH — FAIL-CLOSED (§3 invariant): a controlled consequential execution requires BOTH a verified
    # ActionIntent AND a verified AuthorityDecision. Either missing → EXCEPTION, NEVER an inferred FULFILLMENT.
    # A FULFILLMENT that skipped the authority gate is exactly the bypass we defend against.
    if action_intent_ref is None:
        return _pacro_veb(
            event,
            ctx=verified_ctx,
            hash_profile_id=hash_profile_id,
            profile=profile,
            event_class="EXCEPTION",
            facts=facts_base,
            exception_semantics={
                "exception_type": "MISSING_ACTION_INTENT",
                "detail": "a consequential success cannot be recorded as FULFILLMENT without an ActionIntent",
                "fail_closed": True,
            },
        )
    if not authority_decision_verified:
        return _pacro_veb(
            event,
            ctx=verified_ctx,
            hash_profile_id=hash_profile_id,
            profile=profile,
            event_class="EXCEPTION",
            facts=facts_base,
            exception_semantics={
                "exception_type": "MISSING_AUTHORITY_DECISION",
                "detail": "a consequential success with an ActionIntent but no verified AuthorityDecision "
                          "cannot be a FULFILLMENT — the authority gate must not be bypassed",
                "fail_closed": True,
            },
        )

    # success WITH a verified ActionIntent AND a verified AuthorityDecision → FULFILLMENT
    facts = dict(facts_base)
    facts["action_intent_ref"] = action_intent_ref
    return _pacro_veb(
        event,
        ctx=verified_ctx,
        hash_profile_id=hash_profile_id,
        profile=profile,
        event_class="FULFILLMENT",
        facts=facts,
        exception_semantics={
            "exception_type": "NONE",
            "detail": "no exception — authorized fulfillment of the referenced ActionIntent",
        },
    )
