"""Salesforce REST client for platform runtime token store integrations."""

from __future__ import annotations

import json
import os
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
from urllib.error import HTTPError, URLError
from urllib.parse import quote_plus, urlencode
from urllib.request import Request as UrlRequest, urlopen

from g_gremlin.autopilot.token_store import TokenStore
from g_gremlin.paths import resolve_gremlin_home

DEFAULT_API_VERSION = "62.0"
MIN_CONVERT_LEAD_API_VERSION = (61, 0)


class PlatformSFDCError(RuntimeError):
    """Raised when platform Salesforce REST calls fail."""


@dataclass
class _TokenContext:
    store: TokenStore
    tokens: dict[str, Any]
    refreshed: bool = False


def run_soql(
    soql: str,
    *,
    org_alias: str | None = None,
    state_dir: Path | None = None,
    timeout_seconds: float = 20.0,
) -> list[dict[str, str]]:
    query = str(soql or "").strip()
    if not query:
        raise PlatformSFDCError("SOQL query cannot be empty.")

    context = _load_context(org_alias=org_alias, state_dir=state_dir)
    instance_url = _instance_url(context.tokens)
    api_version = _resolve_api_version(context.tokens)
    url = f"{instance_url}/services/data/v{api_version}/query/?q={quote_plus(query)}"

    rows: list[dict[str, str]] = []
    next_url = url
    while next_url:
        status, payload, text = _authorized_json_get(context, next_url, timeout=timeout_seconds)
        if status >= 400:
            message = _response_error_message(payload, text, default=f"SOQL query failed with HTTP {status}")
            raise PlatformSFDCError(message)
        if not isinstance(payload, dict):
            raise PlatformSFDCError("Salesforce query response was not JSON.")

        for record in payload.get("records") or []:
            if isinstance(record, dict):
                rows.append(_stringify_record(record))

        next_records_url = payload.get("nextRecordsUrl")
        if isinstance(next_records_url, str) and next_records_url.strip():
            next_url = _resolve_next_url(instance_url, next_records_url.strip())
        else:
            next_url = ""
    return rows


def describe_sobject(
    sobject: str,
    *,
    org_alias: str | None = None,
    state_dir: Path | None = None,
    timeout_seconds: float = 20.0,
) -> dict[str, Any]:
    object_name = str(sobject or "").strip()
    if not object_name:
        raise PlatformSFDCError("sobject is required for describe call.")

    context = _load_context(org_alias=org_alias, state_dir=state_dir)
    instance_url = _instance_url(context.tokens)
    api_version = _resolve_api_version(context.tokens)
    url = f"{instance_url}/services/data/v{api_version}/sobjects/{object_name}/describe"
    status, payload, text = _authorized_json_get(context, url, timeout=timeout_seconds)
    if status >= 400:
        message = _response_error_message(payload, text, default=f"Describe call failed with HTTP {status}")
        raise PlatformSFDCError(message)
    if not isinstance(payload, dict):
        raise PlatformSFDCError("Salesforce describe response was not JSON.")
    return payload


def merge_records(
    object_type: str,
    master_id: str,
    victim_ids: list[str] | tuple[str, ...],
    *,
    org_alias: str | None = None,
    state_dir: Path | None = None,
    timeout_seconds: float = 20.0,
) -> dict[str, Any]:
    normalized_object_type = str(object_type or "").strip().lower()
    if normalized_object_type not in {"account", "contact", "lead"}:
        raise PlatformSFDCError("Salesforce merge currently supports Account, Contact, and Lead only.")

    normalized_master_id = str(master_id or "").strip()
    if not normalized_master_id:
        raise PlatformSFDCError("master_id is required for merge.")

    normalized_victims: list[str] = []
    seen_victims: set[str] = set()
    for raw_value in victim_ids or []:
        candidate = str(raw_value or "").strip()
        if not candidate or candidate == normalized_master_id or candidate in seen_victims:
            continue
        seen_victims.add(candidate)
        normalized_victims.append(candidate)
    if not normalized_victims:
        raise PlatformSFDCError("At least one victim id is required for merge.")
    max_victims = 1 if normalized_object_type == "lead" else 2
    if len(normalized_victims) > max_victims:
        raise PlatformSFDCError(
            f"Salesforce {normalized_object_type.title()} merge supports at most {max_victims} victim record(s) per call."
        )

    context = _load_context(org_alias=org_alias, state_dir=state_dir)
    instance_url = _instance_url(context.tokens)
    api_version = _resolve_api_version(context.tokens)
    object_name = normalized_object_type.title()
    url = f"{instance_url}/services/data/v{api_version}/sobjects/{object_name}/merge"
    body = json.dumps(
        {
            "masterRecord": {"Id": normalized_master_id},
            "recordToMergeIds": normalized_victims,
        }
    ).encode("utf-8")
    status, payload, text = _authorized_json_request(
        context,
        "POST",
        url,
        headers={"Content-Type": "application/json"},
        body=body,
        timeout=timeout_seconds,
    )
    if status >= 400:
        message = _response_error_message(payload, text, default=f"Merge call failed with HTTP {status}")
        raise PlatformSFDCError(message)
    if isinstance(payload, dict) and payload:
        return payload
    return {
        "success": True,
        "status_code": status,
        "object_type": object_name,
        "master_id": normalized_master_id,
        "victim_ids": normalized_victims,
    }


def convert_lead(
    lead_id: str,
    *,
    converted_contact_id: str,
    converted_status: str,
    converted_account_id: str | None = None,
    do_not_create_opportunity: bool = True,
    org_alias: str | None = None,
    state_dir: Path | None = None,
    timeout_seconds: float = 20.0,
) -> dict[str, Any]:
    normalized_lead_id = str(lead_id or "").strip()
    normalized_contact_id = str(converted_contact_id or "").strip()
    normalized_status = str(converted_status or "").strip()
    normalized_account_id = str(converted_account_id or "").strip()
    if not normalized_lead_id:
        raise PlatformSFDCError("lead_id is required for convertLead.")
    if not normalized_contact_id:
        raise PlatformSFDCError("converted_contact_id is required for convertLead.")
    if not normalized_status:
        raise PlatformSFDCError("converted_status is required for convertLead.")

    context = _load_context(org_alias=org_alias, state_dir=state_dir)
    api_version = _resolve_api_version(context.tokens)
    apex_payload = _execute_anonymous_apex(
        context,
        _build_convert_lead_apex(
            lead_id=normalized_lead_id,
            converted_contact_id=normalized_contact_id,
            converted_status=normalized_status,
            converted_account_id=normalized_account_id,
            do_not_create_opportunity=bool(do_not_create_opportunity),
        ),
        api_version=api_version,
        timeout_seconds=timeout_seconds,
    )
    _raise_for_failed_anonymous_apex(apex_payload, operation="convertLead")
    lead_state = _fetch_lead_conversion_state(
        normalized_lead_id,
        org_alias=org_alias,
        state_dir=state_dir,
        timeout_seconds=timeout_seconds,
    )
    return _build_convert_lead_response(
        lead_id=normalized_lead_id,
        converted_contact_id=normalized_contact_id,
        converted_account_id=normalized_account_id,
        converted_status=normalized_status,
        do_not_create_opportunity=bool(do_not_create_opportunity),
        lead_state=lead_state,
        execution_method="tooling_execute_anonymous",
    )


def download_content_version_data(
    version_id: str,
    *,
    org_alias: str | None = None,
    state_dir: Path | None = None,
    timeout_seconds: float = 30.0,
) -> bytes:
    normalized_version_id = str(version_id or "").strip()
    if not normalized_version_id:
        raise PlatformSFDCError("version_id is required for content download.")

    context = _load_context(org_alias=org_alias, state_dir=state_dir)
    instance_url = _instance_url(context.tokens)
    api_version = _resolve_api_version(context.tokens)
    url = (
        f"{instance_url}/services/data/v{api_version}/sobjects/ContentVersion/"
        f"{quote_plus(normalized_version_id)}/VersionData"
    )
    status, payload = _authorized_bytes_get(context, url, timeout=timeout_seconds)
    if status >= 400:
        message = payload.decode("utf-8", errors="replace").strip() or f"Content download failed with HTTP {status}"
        raise PlatformSFDCError(message)
    return payload


def download_attachment_body(
    attachment_id: str,
    *,
    org_alias: str | None = None,
    state_dir: Path | None = None,
    timeout_seconds: float = 30.0,
) -> bytes:
    normalized_attachment_id = str(attachment_id or "").strip()
    if not normalized_attachment_id:
        raise PlatformSFDCError("attachment_id is required for attachment download.")

    context = _load_context(org_alias=org_alias, state_dir=state_dir)
    instance_url = _instance_url(context.tokens)
    api_version = _resolve_api_version(context.tokens)
    url = (
        f"{instance_url}/services/data/v{api_version}/sobjects/Attachment/"
        f"{quote_plus(normalized_attachment_id)}/Body"
    )
    status, payload = _authorized_bytes_get(context, url, timeout=timeout_seconds)
    if status >= 400:
        message = payload.decode("utf-8", errors="replace").strip() or f"Attachment download failed with HTTP {status}"
        raise PlatformSFDCError(message)
    return payload


def _execute_anonymous_apex(
    context: _TokenContext,
    apex_code: str,
    *,
    api_version: str,
    timeout_seconds: float,
) -> dict[str, Any]:
    instance_url = _instance_url(context.tokens)
    url = (
        f"{instance_url}/services/data/v{api_version}/tooling/executeAnonymous/"
        f"?anonymousBody={quote_plus(apex_code)}"
    )
    status, payload, text = _authorized_json_get(context, url, timeout=timeout_seconds)
    if status >= 400:
        message = _response_error_message(payload, text, default=f"Anonymous Apex failed with HTTP {status}")
        raise PlatformSFDCError(message)
    if not isinstance(payload, dict):
        raise PlatformSFDCError("Anonymous Apex response was not JSON.")
    return payload


def _fetch_lead_conversion_state(
    lead_id: str,
    *,
    org_alias: str | None,
    state_dir: Path | None,
    timeout_seconds: float,
) -> dict[str, str]:
    rows = run_soql(
        (
            "SELECT Id, IsConverted, Status, ConvertedContactId, ConvertedAccountId, ConvertedOpportunityId "
            f"FROM Lead WHERE Id = '{_escape_soql_literal(lead_id)}' LIMIT 1"
        ),
        org_alias=org_alias,
        state_dir=state_dir,
        timeout_seconds=timeout_seconds,
    )
    return rows[0] if rows else {}


def _raise_for_failed_anonymous_apex(payload: dict[str, Any], *, operation: str) -> None:
    if not payload.get("compiled", False):
        raise PlatformSFDCError(
            str(payload.get("compileProblem") or f"Anonymous Apex failed to compile for {operation}.")
        )
    if not payload.get("success", False):
        raise PlatformSFDCError(
            str(payload.get("exceptionMessage") or f"Anonymous Apex execution failed for {operation}.")
        )


def _build_convert_lead_response(
    *,
    lead_id: str,
    converted_contact_id: str,
    converted_account_id: str,
    converted_status: str,
    do_not_create_opportunity: bool,
    lead_state: dict[str, str],
    execution_method: str,
) -> dict[str, Any]:
    resolved_contact_id = str(lead_state.get("ConvertedContactId") or converted_contact_id).strip()
    resolved_account_id = str(lead_state.get("ConvertedAccountId") or converted_account_id).strip()
    resolved_status = str(lead_state.get("Status") or converted_status).strip()
    resolved_opp_id = str(lead_state.get("ConvertedOpportunityId") or "").strip()
    is_converted = str(lead_state.get("IsConverted") or "").strip().lower() == "true"
    payload: dict[str, Any] = {
        "success": True,
        "status_code": 200,
        "lead_id": lead_id,
        "converted_contact_id": resolved_contact_id,
        "converted_account_id": resolved_account_id,
        "converted_status": resolved_status,
        "do_not_create_opportunity": bool(do_not_create_opportunity),
        "is_converted": is_converted,
        "execution_method": execution_method,
    }
    if resolved_opp_id:
        payload["converted_opportunity_id"] = resolved_opp_id
    return payload


def _build_convert_lead_apex(
    *,
    lead_id: str,
    converted_contact_id: str,
    converted_status: str,
    converted_account_id: str,
    do_not_create_opportunity: bool,
) -> str:
    do_not_create_opportunity_literal = "true" if do_not_create_opportunity else "false"
    return "\n".join(
        [
            f"String gremlinLeadId = '{_escape_apex_literal(lead_id)}';",
            f"String gremlinContactId = '{_escape_apex_literal(converted_contact_id)}';",
            f"String gremlinStatus = '{_escape_apex_literal(converted_status)}';",
            f"String gremlinAccountId = '{_escape_apex_literal(converted_account_id)}';",
            "if (String.isBlank(gremlinAccountId) && !String.isBlank(gremlinContactId)) {",
            "    List<Contact> gremlinContacts = [SELECT AccountId FROM Contact WHERE Id = :gremlinContactId LIMIT 1];",
            "    if (!gremlinContacts.isEmpty() && gremlinContacts[0].AccountId != null) {",
            "        gremlinAccountId = gremlinContacts[0].AccountId;",
            "    }",
            "}",
            "Database.LeadConvert gremlinConvert = new Database.LeadConvert();",
            "gremlinConvert.setLeadId(gremlinLeadId);",
            "gremlinConvert.setContactId(gremlinContactId);",
            "gremlinConvert.setConvertedStatus(gremlinStatus);",
            f"gremlinConvert.setDoNotCreateOpportunity({do_not_create_opportunity_literal});",
            "if (!String.isBlank(gremlinAccountId)) {",
            "    gremlinConvert.setAccountId(gremlinAccountId);",
            "}",
            "Database.LeadConvertResult gremlinResult = Database.convertLead(gremlinConvert);",
            "String gremlinErrors = '';",
            "if (!gremlinResult.isSuccess()) {",
            "    for (Database.Error gremlinError : gremlinResult.getErrors()) {",
            "        gremlinErrors += String.valueOf(gremlinError.getStatusCode()) + ': ' + gremlinError.getMessage() + '; ';",
            "    }",
            "}",
            "System.assert(gremlinResult.isSuccess(), gremlinErrors);",
        ]
    )


def _escape_apex_literal(value: str) -> str:
    return (
        str(value or "")
        .replace("\\", "\\\\")
        .replace("'", "\\'")
        .replace("\r", "\\r")
        .replace("\n", "\\n")
    )


def _escape_soql_literal(value: str) -> str:
    return str(value or "").replace("\\", "\\\\").replace("'", "\\'")


def _load_context(*, org_alias: str | None, state_dir: Path | None) -> _TokenContext:
    store = TokenStore(_resolve_state_dir(state_dir))
    tokens = store.load_tokens("salesforce") or {}
    if not tokens:
        raise PlatformSFDCError("Salesforce OAuth tokens are not configured in the platform token store.")

    context = _TokenContext(store=store, tokens=tokens)
    if not _access_token(context.tokens):
        context.tokens = _refresh_salesforce_tokens(context)
        context.refreshed = True

    if not _access_token(context.tokens):
        raise PlatformSFDCError("Salesforce access token is missing from platform token store.")
    if not _instance_url(context.tokens):
        raise PlatformSFDCError("Salesforce instance_url is missing from platform token store.")
    return context


def _resolve_state_dir(explicit: Path | None) -> Path:
    if explicit is not None:
        return Path(explicit).resolve()
    raw = os.getenv("G_GREMLIN_STATE_DIR", "").strip()
    if raw:
        expanded = os.path.expandvars(os.path.expanduser(raw))
        return Path(expanded).resolve()
    return (resolve_gremlin_home() / "autopilot").resolve()


def _access_token(tokens: dict[str, Any]) -> str:
    return str(tokens.get("access_token") or "").strip()


def _instance_url(tokens: dict[str, Any]) -> str:
    return str(tokens.get("instance_url") or "").strip().rstrip("/")


def _resolve_api_version(tokens: dict[str, Any]) -> str:
    raw = str(
        tokens.get("api_version")
        or os.getenv("G_GREMLIN_SALESFORCE_API_VERSION", "").strip()
        or DEFAULT_API_VERSION
    ).strip()
    normalized = raw.lstrip("vV")
    return normalized or DEFAULT_API_VERSION


def _parse_api_version(value: str) -> tuple[int, int]:
    normalized = str(value or "").strip().lstrip("vV")
    if not normalized:
        return (0, 0)
    parts = normalized.split(".")
    try:
        major = int(parts[0])
    except Exception:
        return (0, 0)
    minor = 0
    if len(parts) > 1:
        try:
            minor = int(parts[1])
        except Exception:
            minor = 0
    return (major, minor)


def _ensure_convert_lead_api_supported(api_version: str) -> None:
    parsed = _parse_api_version(api_version)
    if parsed < MIN_CONVERT_LEAD_API_VERSION:
        raise PlatformSFDCError(
            "Salesforce convertLead REST requires API version 61.0 or newer; "
            f"resolved org API version was {api_version}."
        )


def _authorized_json_get(
    context: _TokenContext,
    url: str,
    *,
    timeout: float = 20.0,
) -> tuple[int, dict[str, Any] | list[Any] | None, str]:
    return _authorized_json_request(context, "GET", url, timeout=timeout)


def _authorized_json_request(
    context: _TokenContext,
    method: str,
    url: str,
    *,
    headers: dict[str, str] | None = None,
    body: bytes | None = None,
    timeout: float = 20.0,
) -> tuple[int, dict[str, Any] | list[Any] | None, str]:
    request_headers = _auth_headers(_access_token(context.tokens))
    if headers:
        request_headers.update(headers)
    status, payload, text = _http_request_json(
        method,
        url,
        headers=request_headers,
        body=body,
        timeout=timeout,
    )
    if status != 401 or context.refreshed:
        return status, payload, text

    context.tokens = _refresh_salesforce_tokens(context, timeout_seconds=timeout)
    context.refreshed = True
    request_headers = _auth_headers(_access_token(context.tokens))
    if headers:
        request_headers.update(headers)
    return _http_request_json(
        method,
        url,
        headers=request_headers,
        body=body,
        timeout=timeout,
    )


def _authorized_bytes_get(
    context: _TokenContext,
    url: str,
    *,
    timeout: float = 30.0,
) -> tuple[int, bytes]:
    status, payload = _http_request_raw(
        "GET",
        url,
        headers=_binary_auth_headers(_access_token(context.tokens)),
        timeout=timeout,
    )
    if status != 401 or context.refreshed:
        return status, payload

    context.tokens = _refresh_salesforce_tokens(context, timeout_seconds=timeout)
    context.refreshed = True
    return _http_request_raw(
        "GET",
        url,
        headers=_binary_auth_headers(_access_token(context.tokens)),
        timeout=timeout,
    )


def _auth_headers(token: str) -> dict[str, str]:
    return {
        "Accept": "application/json",
        "Authorization": f"Bearer {token}",
    }


def _binary_auth_headers(token: str) -> dict[str, str]:
    return {
        "Accept": "application/octet-stream",
        "Authorization": f"Bearer {token}",
    }


def _refresh_salesforce_tokens(
    context: _TokenContext,
    timeout_seconds: float = 20.0,
) -> dict[str, Any]:
    current = dict(context.tokens)
    refresh_token = str(current.get("refresh_token") or "").strip()
    if not refresh_token:
        raise PlatformSFDCError("Salesforce refresh_token is missing; cannot refresh access token.")

    client_id = (
        os.getenv("G_GREMLIN_SALESFORCE_OAUTH_CLIENT_ID", "").strip()
        or os.getenv("G_GREMLIN_SALESFORCE_CLIENT_ID", "").strip()
    )
    client_secret = (
        os.getenv("G_GREMLIN_SALESFORCE_OAUTH_CLIENT_SECRET", "").strip()
        or os.getenv("G_GREMLIN_SALESFORCE_CLIENT_SECRET", "").strip()
    )
    if not client_id or not client_secret:
        raise PlatformSFDCError("Salesforce OAuth refresh is not configured (missing client id/secret).")

    token_url = (
        os.getenv("G_GREMLIN_SALESFORCE_OAUTH_TOKEN_URL", "").strip()
        or "https://login.salesforce.com/services/oauth2/token"
    )
    body = urlencode(
        {
            "grant_type": "refresh_token",
            "client_id": client_id,
            "client_secret": client_secret,
            "refresh_token": refresh_token,
        }
    ).encode("utf-8")
    status, payload, text = _http_request_json(
        "POST",
        token_url,
        headers={
            "Accept": "application/json",
            "Content-Type": "application/x-www-form-urlencoded",
        },
        body=body,
        timeout=timeout_seconds,
    )
    if status >= 400:
        message = _response_error_message(payload, text, default=f"Salesforce token refresh failed with HTTP {status}")
        raise PlatformSFDCError(message)
    if not isinstance(payload, dict):
        raise PlatformSFDCError("Salesforce refresh response was not JSON.")

    access_token = str(payload.get("access_token") or "").strip()
    if not access_token:
        raise PlatformSFDCError("Salesforce refresh response did not include access_token.")

    current["access_token"] = access_token
    current["refreshed_at"] = datetime.now(timezone.utc).isoformat()
    new_refresh = str(payload.get("refresh_token") or "").strip()
    if new_refresh:
        current["refresh_token"] = new_refresh
    instance_url = str(payload.get("instance_url") or "").strip()
    if instance_url:
        current["instance_url"] = instance_url
    scope = str(payload.get("scope") or "").strip()
    if scope:
        current["scope"] = scope
    token_type = str(payload.get("token_type") or "").strip()
    if token_type:
        current["token_type"] = token_type
    expires_in = _coerce_positive_int(payload.get("expires_in"))
    if expires_in is not None:
        current["expires_in"] = expires_in

    context.store.save_tokens("salesforce", current)
    latest = context.store.load_tokens("salesforce") or {}
    if not latest:
        raise PlatformSFDCError("Salesforce token refresh completed but updated tokens could not be loaded.")
    return latest


def _coerce_positive_int(value: Any) -> int | None:
    if value in (None, ""):
        return None
    try:
        parsed = int(str(value))
    except Exception:
        return None
    return parsed if parsed > 0 else None


def _http_request(
    method: str,
    url: str,
    *,
    headers: dict[str, str] | None = None,
    body: bytes | None = None,
    timeout: float = 20.0,
) -> tuple[int, str]:
    request = UrlRequest(url, data=body, headers=headers or {}, method=method)
    try:
        with urlopen(request, timeout=timeout) as response:
            text = response.read().decode("utf-8", errors="replace")
            return int(getattr(response, "status", 200)), text
    except HTTPError as exc:
        return int(exc.code), exc.read().decode("utf-8", errors="replace")
    except URLError as exc:
        raise PlatformSFDCError(f"HTTP request failed: {exc.reason}") from exc


def _http_request_raw(
    method: str,
    url: str,
    *,
    headers: dict[str, str] | None = None,
    body: bytes | None = None,
    timeout: float = 20.0,
) -> tuple[int, bytes]:
    request = UrlRequest(url, data=body, headers=headers or {}, method=method)
    try:
        with urlopen(request, timeout=timeout) as response:
            return int(getattr(response, "status", 200)), response.read()
    except HTTPError as exc:
        return int(exc.code), exc.read()
    except URLError as exc:
        raise PlatformSFDCError(f"HTTP request failed: {exc.reason}") from exc


def _http_request_json(
    method: str,
    url: str,
    *,
    headers: dict[str, str] | None = None,
    body: bytes | None = None,
    timeout: float = 20.0,
) -> tuple[int, dict[str, Any] | list[Any] | None, str]:
    status, text = _http_request(method, url, headers=headers, body=body, timeout=timeout)
    if not text.strip():
        return status, None, text
    try:
        payload = json.loads(text)
    except Exception:
        payload = None
    return status, payload, text


def _response_error_message(payload: Any, text: str, *, default: str) -> str:
    if isinstance(payload, dict):
        for key in ("error_description", "error", "message", "detail"):
            value = payload.get(key)
            if isinstance(value, str) and value.strip():
                return value.strip()
    if isinstance(payload, list) and payload:
        first = payload[0]
        if isinstance(first, dict):
            for key in ("message", "errorCode", "error"):
                value = first.get(key)
                if isinstance(value, str) and value.strip():
                    return value.strip()
    stripped = text.strip()
    if stripped:
        return stripped
    return default


def _resolve_next_url(instance_url: str, next_records_url: str) -> str:
    if next_records_url.startswith(("https://", "http://")):
        return next_records_url
    if not next_records_url.startswith("/"):
        next_records_url = "/" + next_records_url
    return f"{instance_url}{next_records_url}"


def _stringify_record(record: dict[str, Any]) -> dict[str, str]:
    flattened: dict[str, Any] = {}
    _flatten_record(record, prefix="", out=flattened)
    return {key: _stringify_scalar(value) for key, value in flattened.items()}


def _flatten_record(record: dict[str, Any], *, prefix: str, out: dict[str, Any]) -> None:
    for key, value in record.items():
        if key == "attributes":
            continue
        nested_key = f"{prefix}.{key}" if prefix else key
        if isinstance(value, dict):
            _flatten_record(value, prefix=nested_key, out=out)
            continue
        out[nested_key] = value


def _stringify_scalar(value: Any) -> str:
    if value is None:
        return ""
    if isinstance(value, bool):
        return "true" if value else "false"
    if isinstance(value, (int, float)):
        return str(value)
    if isinstance(value, (list, dict)):
        return json.dumps(value, separators=(",", ":"), sort_keys=True)
    return str(value)


__all__ = [
    "PlatformSFDCError",
    "convert_lead",
    "describe_sobject",
    "download_attachment_body",
    "download_content_version_data",
    "merge_records",
    "run_soql",
]

