"""Safe chat diagnostics: structured metadata only; never provider text or URLs."""
import json
import logging
import re
from uuid import uuid4
import httpx

__all__ = ['ProviderError', 'new_request_id', 'log_failure', 'public_error',
           'install_safe_logging']
_CODES = {'provider_payment_required', 'provider_authentication', 'provider_access_denied',
          'provider_model_unavailable', 'provider_rate_limited', 'provider_unavailable',
          'provider_rejected', 'provider_timeout', 'provider_network',
          'provider_malformed_response', 'provider_content_blocked', 'internal_error', 'service_unavailable'}
_REASONS = {'unspecified', 'prepaid_credits_depleted', 'billing_disabled',
            'api_key_invalid', 'model_not_found', 'access_denied', 'quota_exceeded'}

def new_request_id():
    return uuid4().hex

class ProviderError(Exception):
    def __init__(self, code, status=None, reason='unspecified'):
        self.code = code if code in _CODES else 'internal_error'
        self.status = status if type(status) is int and 100 <= status <= 599 else None
        self.reason = reason if reason in _REASONS else 'unspecified'
        self.retryable = self.code in {'provider_timeout', 'provider_network',
            'provider_rate_limited', 'provider_unavailable'}
        super().__init__(self.code)

    @classmethod
    def from_response(cls, response):
        status = response.status_code
        code = {401:'provider_authentication', 402:'provider_payment_required',
                403:'provider_access_denied', 404:'provider_model_unavailable',
                429:'provider_rate_limited'}.get(status,
                'provider_unavailable' if status >= 500 else 'provider_rejected')
        reason = 'unspecified'
        # Inspect only in memory. Never retain, return or log the provider payload.
        try:
            error = response.json().get('error', {})
            message = error.get('message', '')
            if not isinstance(message, str): message = ''
            message = message.lower()
            if status == 402 and ('prepayment credits are depleted' in message or
                                  'prepay credit balance is depleted' in message):
                reason = 'prepaid_credits_depleted'
            details = error.get('details', [])
            reasons = {d.get('reason') for d in details if isinstance(d, dict)
                       and isinstance(d.get('reason'), str)} if isinstance(details, list) else set()
            for value, safe in [('BILLING_DISABLED','billing_disabled'),
                                ('API_KEY_INVALID','api_key_invalid'),
                                ('MODEL_NOT_FOUND','model_not_found'),
                                ('ACCESS_DENIED','access_denied'),
                                ('QUOTA_EXCEEDED','quota_exceeded')]:
                if value in reasons: reason = safe; break
        except (ValueError, TypeError, AttributeError):
            pass
        return cls(code, status, reason)

    @classmethod
    def from_exception(cls, error):
        if isinstance(error, cls): return error
        if isinstance(error, httpx.TimeoutException): return cls('provider_timeout')
        if isinstance(error, httpx.HTTPStatusError): return cls.from_response(error.response)
        if isinstance(error, httpx.RequestError): return cls('provider_network')
        return cls('internal_error')

def log_failure(logger, error, request_id, event='chat_failed', attempt=None):
    error = ProviderError.from_exception(error)
    data = {'event': event if event in {'chat_failed','chat_fallback','provider_retry',
             'healthcheck_failed','conversation_log_failed'} else 'chat_failed',
            'request_id': request_id if re.fullmatch(r'[a-f0-9]{32}',request_id or '') else new_request_id(),
            'code': error.code, 'upstream_status': error.status,
            'reason': error.reason, 'retryable': error.retryable}
    if type(attempt) is int: data['attempt'] = attempt
    logger.warning(json.dumps(data, sort_keys=True))

def public_error(error, request_id):
    error = ProviderError.from_exception(error)
    return {'code': error.code, 'request_id': request_id, 'retryable': error.retryable,
            'message': 'Chat is temporarily unavailable. Please try again later. Reference: '+request_id}

class _TransportPrivacyFilter(logging.Filter):
    def filter(self, record):
        # httpcore DEBUG can include authorization headers; httpx can include query keys.
        # Request status is logged separately through allowlisted structured events.
        return not (record.name == 'httpx' or record.name.startswith('httpx.') or
                    record.name == 'httpcore' or record.name.startswith('httpcore.'))

_transport_filter = _TransportPrivacyFilter()

def install_safe_logging():
    loggers = [logging.getLogger()]
    loggers += [v for k,v in logging.Logger.manager.loggerDict.items()
                if isinstance(v,logging.Logger) and k.startswith(('httpx','httpcore'))]
    for logger in loggers:
        if logger.name.startswith(('httpx','httpcore')):
            logger.addFilter(_transport_filter)
        for handler in logger.handlers:
            handler.addFilter(_transport_filter)
