"""ContextResolver — supplies (Domain, AuthorityClass, UserClass) from request context.

ROLE
----
ContextResolver answers: "What governance corridor are we in?"

It does NOT answer: "What should the system do?"
That is the Governor's job.

INPUTS (in cascade priority order)
-----------------------------------
1. Explicit context — domain_var ContextVar set by a route handler
   (e.g. /v1/medical/chat sets Domain.MEDICAL before lens.process())

2. Rule context — domain derived from the flag's rule_id or ESCALATION_ROUTES
   (available when a flag carries a rule that names its domain)

3. Flag-pattern fallback — domain inferred from the flag type taxonomy
   (deterministic table; see _FLAG_DOMAIN_MAP)

4. Default — Domain.GENERAL, AuthorityClass.GP, UserClass.GENERAL

PROVENANCE
----------
Every resolved context carries a ContextResolutionProvenance record that
names where each value came from. This makes forensic reading possible —
reviewers can see whether a domain was deliberately routed or fell through
to a flag-pattern guess. See runtime_types.ContextResolutionProvenance.

NEVER-RAISES CONTRACT
---------------------
ContextResolver must never raise an exception. All four cascade levels end in
safe defaults. Governance context resolution should degrade gracefully, not
explode theatrically. If a ContextVar contains an invalid value, fall through
to the next level rather than raising.

BLEED PREVENTION
----------------
The three ContextVars (domain_var, authority_class_var, user_class_var) must be
reset in a finally block after each request. The proxy is responsible for this
using the standard token.set() / token.reset() pattern already used for
trace_id_var and session_id_var. Failure to reset will bleed one request's
corridor into the next — including authority upgrades, which would be a
governance bug with real consequences.
"""

from __future__ import annotations

from aurora_lens.governor.models import AuthorityClass, Domain, UserClass
from aurora_lens.verify.flags import Flag, FlagType
from aurora_lens.govern.adapters.runtime_types import ContextResolutionProvenance

# Import the three ContextVars added to context.py.
# These are populated by the proxy request lifecycle.
from aurora_lens.context import (
    domain_var,
    authority_class_var,
    user_class_var,
)


# ── Flag-type → Domain mapping (declarative table, not scattered through code) ─

# Priority within the table: flags are matched in definition order when
# multiple flags are present. STOP-class flags take domain precedence
# (they are listed first) over REFUSE/ASK-class flags.
# This is intentional: if a medical dosage flag and an epistemic flag both
# fire, the domain should be medical (more restrictive corridor), not general.

_FLAG_DOMAIN_PRIORITY: list[tuple[FlagType, Domain]] = [
    # Medical — hard-stop-always content vetoes
    (FlagType.SELF_HARM_INSTRUCTION,           Domain.MEDICAL),
    (FlagType.MEDICAL_DOSAGE_RECOMMENDATION,   Domain.MEDICAL),
    (FlagType.PEDIATRIC_DOSAGE_RECOMMENDATION, Domain.MEDICAL),
    (FlagType.NUMERIC_MEDICAL_INSTRUCTION,     Domain.MEDICAL),
    (FlagType.EMERGENCY_TRIAGE_GUIDANCE,       Domain.MEDICAL),
    # Legal — hard-stop-always content vetoes
    (FlagType.ILLEGAL_INSTRUCTION,             Domain.LEGAL),
    (FlagType.TARGETED_DEFAMATION,             Domain.LEGAL),
    (FlagType.SENSITIVE_PII_EXPOSURE,          Domain.LEGAL),   # data-protection is a legal/compliance domain
    # Medical — verify-or-refuse
    (FlagType.PERSONALIZED_MEDICAL_ADVICE,     Domain.MEDICAL),
    # Legal — verify-or-refuse
    (FlagType.PERSONALIZED_LEGAL_ADVICE,       Domain.LEGAL),
    # Finance — verify-or-refuse
    (FlagType.PERSONALIZED_FINANCIAL_ADVICE,   Domain.FINANCE),
    # Research — regulatory/epistemic claims
    (FlagType.UNVERIFIED_REGULATORY_CLAIM,     Domain.RESEARCH),
    # Ambiguity — PEF binding failures that need clarification
    (FlagType.UNRESOLVED_REFERENT,             Domain.AMBIGUITY),
    (FlagType.UNRESOLVED_COMPARAND,            Domain.AMBIGUITY),
    # General — extraction / epistemic failures with no domain signal
    # (not listed — these fall through to default Domain.GENERAL)
]

# Convert to a dict for O(1) lookup, preserving first-match priority.
_FLAG_DOMAIN_MAP: dict[FlagType, Domain] = {}
for _ft, _dom in _FLAG_DOMAIN_PRIORITY:
    if _ft not in _FLAG_DOMAIN_MAP:
        _FLAG_DOMAIN_MAP[_ft] = _dom


def _resolve_domain(flags: list[Flag]) -> tuple[Domain, str]:
    """Resolve Domain using the four-level cascade. Returns (domain, source)."""

    # Level 1: Explicit context via ContextVar (set by route handler).
    raw_domain = domain_var.get(None)
    if raw_domain is not None:
        try:
            if isinstance(raw_domain, Domain):
                return raw_domain, "explicit"
            return Domain(str(raw_domain)), "explicit"
        except (ValueError, KeyError):
            pass  # Invalid value — fall through

    # Level 2: Rule context — not yet implemented at the flag level;
    # flags do not currently carry a rule_id with domain metadata.
    # Reserved for future: when PolicyRule gains a domain field, look it up here.

    # Level 3: Flag-pattern fallback — first matching flag in priority order.
    # Flags are checked against _FLAG_DOMAIN_MAP in table order, not input order,
    # to ensure deterministic domain classification regardless of extraction order.
    for flag_type, domain in _FLAG_DOMAIN_MAP.items():
        for flag in flags:
            if flag.flag_type == flag_type:
                return domain, "flag_pattern"

    # Level 4: Default.
    return Domain.GENERAL, "default"


def _resolve_authority_class() -> tuple[AuthorityClass, str]:
    """Resolve AuthorityClass from ContextVar set at startup. Returns (cls, source)."""
    raw = authority_class_var.get(None)
    if raw is not None:
        try:
            if isinstance(raw, AuthorityClass):
                return raw, "config"
            return AuthorityClass(str(raw)), "config"
        except (ValueError, KeyError):
            pass  # Invalid config value — fall through to default
    return AuthorityClass.GP, "default"


def _resolve_user_class() -> tuple[UserClass, str]:
    """Resolve UserClass from per-request ContextVar. Returns (cls, source)."""
    raw = user_class_var.get(None)
    if raw is not None:
        try:
            if isinstance(raw, UserClass):
                return raw, "header"
            return UserClass(str(raw)), "header"
        except (ValueError, KeyError):
            pass  # Invalid header value — fall through to default
    return UserClass.GENERAL, "default"


class ContextResolver:
    """Resolves governance corridor context from request/config/rule/flag sources.

    Returns (Domain, AuthorityClass, UserClass, ContextResolutionProvenance).
    Never raises. Always returns safe defaults if cascade fails at all levels.

    Usage:
        resolver = ContextResolver()
        domain, authority, user_class, provenance = resolver.resolve(flags)
    """

    def resolve(
        self,
        flags: list[Flag],
    ) -> tuple[Domain, AuthorityClass, UserClass, ContextResolutionProvenance]:
        """Resolve the full governance corridor context.

        Args:
            flags: Verification flags for the current turn (used for flag-pattern
                   fallback only; never used to infer AuthorityClass or UserClass).

        Returns:
            (Domain, AuthorityClass, UserClass, ContextResolutionProvenance)

        NEVER-RAISES CONTRACT: Any exception at any level falls through to the
        next level. The final level always returns safe defaults.
        """
        try:
            domain, domain_source = _resolve_domain(flags)
        except Exception:
            domain, domain_source = Domain.GENERAL, "default"

        try:
            authority, authority_source = _resolve_authority_class()
        except Exception:
            authority, authority_source = AuthorityClass.GP, "default"

        try:
            user_class, user_class_source = _resolve_user_class()
        except Exception:
            user_class, user_class_source = UserClass.GENERAL, "default"

        provenance = ContextResolutionProvenance(
            domain_source=domain_source,
            authority_source=authority_source,
            user_class_source=user_class_source,
        )

        return domain, authority, user_class, provenance
