"""Low-level HTTP client for the governance evaluation endpoint."""

from __future__ import annotations

import json
import logging

from typing import Any

import httpx

from lumenova_beacon.exceptions import (
    GovernanceError,
    GovernanceNotFoundError,
    GovernanceValidationError,
)
from lumenova_beacon.utils.client_helpers import get_base_url, get_transport
from lumenova_beacon.utils.http_errors import HTTPErrorHandler


logger = logging.getLogger(__name__)

_error_handler = HTTPErrorHandler(
    not_found_exc=GovernanceNotFoundError,
    validation_exc=GovernanceValidationError,
    base_exc=GovernanceError,
)

ENDPOINT = '/api/v1/governance/evaluate'


class GovernanceClient:
    """Client for ``POST /api/v1/governance/evaluate``.

    Provides both synchronous and asynchronous methods for evaluating
    agent actions against governance policies via the Beacon backend.

    The client reuses the active :class:`BeaconClient` transport for
    authentication headers, endpoint URL, and TLS settings.
    """

    def _build_url(self) -> str:
        return f'{get_base_url()}{ENDPOINT}'

    def _build_payload(
        self,
        phase: str,
        type_: str,
        stack_ids: list[str] | None,
        tags: list[str] | None,
        extra: dict[str, Any],
        trace_id: str | None = None,
        span_id: str | None = None,
    ) -> dict[str, Any]:
        if stack_ids is None and tags is None:
            raise GovernanceValidationError('At least one of stack_ids or tags must be provided')
        input_data: dict[str, Any] = {'phase': phase, 'type': type_, **extra}
        payload: dict[str, Any] = {'input': input_data}
        if stack_ids is not None:
            payload['stack_ids'] = stack_ids
        if tags is not None:
            payload['tags'] = tags
        if trace_id is not None:
            payload['trace_id'] = trace_id
        if span_id is not None:
            payload['span_id'] = span_id
        return payload

    def evaluate(
        self,
        phase: str,
        type_: str,
        stack_ids: list[str] | None = None,
        tags: list[str] | None = None,
        extra: dict[str, Any] | None = None,
        timeout: float | None = None,
        trace_id: str | None = None,
        span_id: str | None = None,
    ) -> dict[str, Any]:
        """Evaluate an action against governance policies (sync).

        Args:
            phase: ``'pre'``, ``'post'``, or ``'atomic'``.
            type_: ``'tool'``, ``'llm'``, ``'handoff'``, etc.
            stack_ids: Explicit policy stack IDs to evaluate against.
            tags: Tag-based policy stack discovery.
            extra: Additional fields merged into the ``input`` object
                (e.g. ``source``, ``context``, ``tool``).
            timeout: Override request timeout in seconds.
            trace_id: Distributed trace identifier for correlation.
            span_id: Span identifier for precise trace correlation.

        Returns:
            The full API response dictionary containing ``decision``,
            ``message``, ``rules_evaluated``, ``latency_ms``, etc.

        Raises:
            GovernanceError: On unexpected API errors.
            GovernanceNotFoundError: If the governance resource is not found.
            GovernanceValidationError: If the request is invalid.
        """
        url = self._build_url()
        transport = get_transport('Governance operations')
        payload = self._build_payload(
            phase,
            type_,
            stack_ids,
            tags,
            extra or {},
            trace_id=trace_id,
            span_id=span_id,
        )
        request_timeout = timeout if timeout is not None else transport.timeout

        logger.debug(
            'Governance evaluate request: POST %s | stack_ids=%s tags=%s phase=%s type=%s timeout=%s',
            url,
            stack_ids,
            tags,
            phase,
            type_,
            request_timeout,
        )
        logger.debug('Governance evaluate payload: %s', payload)

        try:
            response = httpx.post(
                url,
                json=payload,
                headers=transport.headers,
                timeout=request_timeout,
                verify=transport.verify,
            )
            logger.debug(
                'Governance evaluate response: status=%s body=%s',
                response.status_code,
                response.text[:500],
            )
            response.raise_for_status()
            return response.json()
        except httpx.HTTPStatusError as e:
            logger.debug(
                'Governance evaluate HTTP error: status=%s body=%s',
                e.response.status_code,
                e.response.text[:500],
            )
            _error_handler.handle(e)
            raise  # unreachable; silences mypy
        except httpx.RequestError as e:
            logger.debug('Governance evaluate connection error: %s', e)
            raise GovernanceError(f'Failed to connect to Beacon Governance API: {e}') from e
        except json.JSONDecodeError as e:
            logger.debug('Governance evaluate JSON decode error: %s', e)
            raise GovernanceError(f'Invalid response from Beacon Governance API: {e}') from e

    async def aevaluate(
        self,
        phase: str,
        type_: str,
        stack_ids: list[str] | None = None,
        tags: list[str] | None = None,
        extra: dict[str, Any] | None = None,
        timeout: float | None = None,
        trace_id: str | None = None,
        span_id: str | None = None,
    ) -> dict[str, Any]:
        """Evaluate an action against governance policies (async).

        Args:
            phase: ``'pre'``, ``'post'``, or ``'atomic'``.
            type_: ``'tool'``, ``'llm'``, ``'handoff'``, etc.
            stack_ids: Explicit policy stack IDs to evaluate against.
            tags: Tag-based policy stack discovery.
            extra: Additional fields merged into the ``input`` object.
            timeout: Override request timeout in seconds.
            trace_id: Distributed trace identifier for correlation.
            span_id: Span identifier for precise trace correlation.

        Returns:
            The full API response dictionary.

        Raises:
            GovernanceError: On unexpected API errors.
            GovernanceNotFoundError: If the governance resource is not found.
            GovernanceValidationError: If the request is invalid.
        """
        url = self._build_url()
        transport = get_transport('Governance operations')
        payload = self._build_payload(
            phase,
            type_,
            stack_ids,
            tags,
            extra or {},
            trace_id=trace_id,
            span_id=span_id,
        )
        request_timeout = timeout if timeout is not None else transport.timeout

        logger.debug(
            'Governance async evaluate request: POST %s | stack_ids=%s tags=%s phase=%s type=%s timeout=%s',
            url,
            stack_ids,
            tags,
            phase,
            type_,
            request_timeout,
        )
        logger.debug('Governance async evaluate payload: %s', payload)

        try:
            async with httpx.AsyncClient(
                timeout=request_timeout, verify=transport.verify
            ) as client:
                response = await client.post(
                    url,
                    json=payload,
                    headers=transport.headers,
                )
                logger.debug(
                    'Governance async evaluate response: status=%s body=%s',
                    response.status_code,
                    response.text[:500],
                )
                response.raise_for_status()
                return response.json()
        except httpx.HTTPStatusError as e:
            logger.debug(
                'Governance async evaluate HTTP error: status=%s body=%s',
                e.response.status_code,
                e.response.text[:500],
            )
            _error_handler.handle(e)
            raise  # unreachable; silences mypy
        except httpx.RequestError as e:
            logger.debug('Governance async evaluate connection error: %s', e)
            raise GovernanceError(f'Failed to connect to Beacon Governance API: {e}') from e
        except json.JSONDecodeError as e:
            logger.debug('Governance async evaluate JSON decode error: %s', e)
            raise GovernanceError(f'Invalid response from Beacon Governance API: {e}') from e
