"""Shared helpers for governance evaluation logic.

Used by both the callback handler and the ``@governance`` decorator.
"""

from __future__ import annotations

import logging
from collections.abc import Callable
from typing import Any

from lumenova_beacon.exceptions import GovernanceError, GovernanceViolationError
from lumenova_beacon.governance.client import GovernanceClient

logger = logging.getLogger(__name__)

MAX_EXTRA_VALUE_LENGTH = 4096


def get_sdk_version() -> str:
    """Return the SDK version string, or ``'unknown'`` if unavailable."""
    try:
        from lumenova_beacon import __version__

        return __version__
    except ImportError:
        return 'unknown'


def truncate(value: str, max_length: int = MAX_EXTRA_VALUE_LENGTH) -> str:
    """Truncate a string for inclusion in evaluation payloads."""
    if len(value) > max_length:
        return value[:max_length]
    return value


def default_result_parser(result: dict[str, Any]) -> bool:
    """Default result parser for the new flat response format.

    The backend returns ``{"decision": "allow|block|apply_guardrail", ...}``.
    """
    decision = result.get('decision', 'allow')
    # Backward compat: handle old nested format if still encountered
    if isinstance(decision, dict):
        return decision.get('action', 'allow') != 'block'
    return decision != 'block'


def is_allowed(
    response: dict[str, Any],
    result_parser: Callable[[dict[str, Any]], bool] | None = None,
) -> bool:
    """Check whether a governance response allows the action."""
    parser = result_parser or default_result_parser
    allowed = parser(response)
    logger.debug(
        'Governance result parsing: response=%s allowed=%s parser=%s',
        response, allowed,
        'custom' if result_parser else 'default',
    )
    return allowed


def evaluate_or_fail_open(
    client: GovernanceClient,
    phase: str,
    type_: str,
    stack_ids: list[str] | None,
    tags: list[str] | None,
    extra: dict[str, Any],
    timeout: float | None,
    fail_open: bool,
    trace_id: str | None = None,
    span_id: str | None = None,
) -> dict[str, Any] | None:
    """Call the governance API; return the response or ``None`` on fail-open."""
    logger.debug(
        'Governance evaluation: phase=%s type=%s stack_ids=%s tags=%s extra_keys=%s',
        phase, type_, stack_ids, tags, list(extra.keys()),
    )
    logger.debug('Governance evaluation extra payload: %s', extra)
    try:
        resp = client.evaluate(
            phase=phase,
            type_=type_,
            stack_ids=stack_ids,
            tags=tags,
            extra=extra,
            timeout=timeout,
            trace_id=trace_id,
            span_id=span_id,
        )
        logger.debug(
            'Governance evaluation succeeded: latency_ms=%s decision=%s',
            resp.get('latency_ms'), resp.get('decision'),
        )
        return resp
    except GovernanceError as e:
        if not fail_open:
            logger.debug('Governance API error (fail-closed), re-raising: %s', e)
            raise
        logger.warning('Governance API error, failing open: %s', e)
        return None


async def aevaluate_or_fail_open(
    client: GovernanceClient,
    phase: str,
    type_: str,
    stack_ids: list[str] | None,
    tags: list[str] | None,
    extra: dict[str, Any],
    timeout: float | None,
    fail_open: bool,
    trace_id: str | None = None,
    span_id: str | None = None,
) -> dict[str, Any] | None:
    """Async version of :func:`evaluate_or_fail_open`."""
    logger.debug(
        'Governance async evaluation: phase=%s type=%s stack_ids=%s tags=%s extra_keys=%s',
        phase, type_, stack_ids, tags, list(extra.keys()),
    )
    logger.debug('Governance async evaluation extra payload: %s', extra)
    try:
        resp = await client.aevaluate(
            phase=phase,
            type_=type_,
            stack_ids=stack_ids,
            tags=tags,
            extra=extra,
            timeout=timeout,
            trace_id=trace_id,
            span_id=span_id,
        )
        logger.debug(
            'Governance async evaluation succeeded: latency_ms=%s decision=%s',
            resp.get('latency_ms'), resp.get('decision'),
        )
        return resp
    except GovernanceError as e:
        if not fail_open:
            logger.debug('Governance async API error (fail-closed), re-raising: %s', e)
            raise
        logger.warning('Governance async API error, failing open: %s', e)
        return None


def enforce(
    response: dict[str, Any] | None,
    phase: str,
    context_msg: str,
    result_parser: Callable[[dict[str, Any]], bool] | None = None,
) -> None:
    """Raise ``GovernanceViolationError`` if the response blocks the action."""
    if response is None:
        logger.debug('Governance enforce skipped: no response (fail-open path)')
        return
    if not is_allowed(response, result_parser):
        rego_message = response.get('message', '')

        msg = f'Governance policy blocked {context_msg} at {phase}'
        if rego_message:
            msg = f'{msg}: {rego_message}'

        logger.debug(
            'Governance BLOCKED: %s at %s | decision=%s rules=%s',
            context_msg, phase, response.get('decision'),
            response.get('rules_evaluated'),
        )
        raise GovernanceViolationError(
            message=msg,
            result=response,
            policies=response.get('rules_evaluated', []),
            latency_ms=response.get('latency_ms', 0.0),
        )
    logger.debug('Governance ALLOWED: %s at %s', context_msg, phase)
