"""
Shared writer for `onboarding_api_call_logs` — persists one masked
request/response row for an outbound payment-service HTTP call.

Used by both producers:
  - BaseBoardingClient (service="Base CRM") — passes already-unwrapped httpx
    kwargs as `request_body`.
  - TSYSProvider (service="TransIT") — passes the raw JSON payload as
    `request_body`.

Best-effort: any failure here is swallowed after a warning so logging never
breaks the real call it wraps. Skips the write when `merchant_id is None`
(nothing the per-merchant export could ever surface).

# ponytail: opens a short-lived sync SessionLocal() per call — a small
# blocking DB write. Fine at onboarding + normal payment volume; move to a
# background queue only if a hot path's call rate makes it measurable.
"""
from __future__ import annotations

import logging
import traceback
from typing import Any, Optional

import httpx

logger = logging.getLogger(__name__)


def record_api_call_log(
    *,
    service: str,
    api_name: Optional[str],
    merchant_id: Optional[int],
    application_id: Optional[int] = None,
    method: str,
    path: str,
    url: str,
    request_headers: Optional[dict],
    request_body: Any,
    response: Optional[httpx.Response] = None,
    error: Optional[BaseException] = None,
    duration_ms: Optional[int] = None,
    correlation_id: Optional[str] = None,
) -> None:
    if merchant_id is None:
        return
    try:
        from src.apps.merchant_onboarding.helpers.log_masking import mask_headers, mask_json
        from src.apps.merchant_onboarding.models.api_call_log import OnboardingApiCallLog
        from src.core.database import SessionLocal

        response_status: Optional[int] = None
        response_headers: Optional[dict] = None
        response_body: Optional[dict] = None
        if response is not None:
            response_status = response.status_code
            response_headers = mask_headers(dict(response.headers))
            content_type = response.headers.get("content-type", "")
            if "application/json" in content_type:
                try:
                    response_body = mask_json(response.json())
                except Exception:
                    response_body = {"_raw": f"<unparseable JSON body, {len(response.content)} bytes>"}
            else:
                response_body = {
                    "_raw": f"<{content_type or 'unknown content-type'}, {len(response.content)} bytes>"
                }

        error_message: Optional[str] = None
        if error is not None:
            if getattr(error, "__traceback__", None) is not None:
                error_message = "".join(
                    traceback.format_exception(type(error), error, error.__traceback__)
                )
            else:
                error_message = str(error)

        db = SessionLocal()
        try:
            db.add(
                OnboardingApiCallLog(
                    service=service,
                    api_name=api_name,
                    merchant_id=merchant_id,
                    application_id=application_id,
                    method=method,
                    path=path,
                    full_url=url,
                    request_headers=mask_headers(request_headers),
                    request_body=mask_json(request_body) if request_body is not None else None,
                    response_status=response_status,
                    response_headers=response_headers,
                    response_body=response_body,
                    duration_ms=duration_ms,
                    correlation_id=correlation_id,
                    error_message=error_message,
                )
            )
            db.commit()
        finally:
            db.close()
    except Exception as exc:  # never let logging break the real call
        logger.warning("record_api_call_log: failed to persist api call log row — %s", exc)
