// ── 3DS finalize HTTP client ────────────────────────────────────
//
// POSTs the 3DS flow outcome back to the host's finalize endpoint
// (e.g., WordPress: `/wp-json/weeconnectpay/v1/woocommerce/3ds/finalize`;
// dashboard: `/api/v1/clover/charges/3ds/finalize`).
//
// Headers are pluggable so each host can inject nonces / auth tokens.
// Response parsing handles three outcomes: terminal success, escalation
// (method→challenge or vice versa), or terminal failure.

import type { HeadersProvider, ThreeDsInterim } from './types';

export interface FinalizeRequest {
  readonly url: string;
  readonly headers?: HeadersProvider;
  readonly body: Record<string, unknown>;
}

export interface FinalizeResponseEnvelope {
  readonly ok: boolean;
  readonly data: FinalizeResponseData | null;
}

export interface FinalizeResponseData {
  readonly success?: boolean;
  readonly redirect?: string;
  readonly message?: string;
  /** When Clover escalates the flow, the host returns the next interim payload here. */
  readonly weeconnectpay_threeds_interim?: ThreeDsInterimRaw;
}

/** Wire shape from Clover / the host before normalization to ThreeDsInterim. */
export interface ThreeDsInterimRaw {
  readonly state: 'method_required' | 'challenge_required';
  readonly charge_id: string | null;
  readonly threeds: {
    readonly acs_url?: string;
    readonly acs_transaction_id?: string;
    readonly method_url?: string;
    readonly method_notification_url?: string;
    readonly threeds_server_transaction_id?: string;
    readonly threeds_protocol_version?: string;
    /** Older Clover docs used `message_version` — both supported. */
    readonly message_version?: string;
  };
}

export async function postFinalize(request: FinalizeRequest): Promise<FinalizeResponseEnvelope> {
  const headers: Record<string, string> = {
    'Content-Type': 'application/json',
    Accept: 'application/json',
  };
  if (request.headers) {
    Object.assign(headers, await request.headers());
  }

  let response: Response;
  try {
    response = await fetch(request.url, {
      method: 'POST',
      headers,
      body: JSON.stringify(request.body),
    });
  } catch (error) {
    throw new Error(`Network error during 3DS finalize: ${(error as Error).message}`);
  }

  let data: FinalizeResponseData | null = null;
  try {
    data = (await response.json()) as FinalizeResponseData;
  } catch {
    data = null;
  }
  return { ok: response.ok, data };
}

/**
 * Normalize the wire shape into the SDK's `ThreeDsInterim`. Returns null when
 * required fields (charge_id) are missing — the caller should treat as a
 * failure rather than entering a malformed state.
 */
export function parseInterim(raw: ThreeDsInterimRaw): ThreeDsInterim | null {
  if (!raw.charge_id) {
    return null;
  }
  return {
    state: raw.state,
    chargeId: raw.charge_id,
    fields: {
      acsUrl: raw.threeds.acs_url,
      acsTransactionId: raw.threeds.acs_transaction_id,
      methodUrl: raw.threeds.method_url,
      methodNotificationUrl: raw.threeds.method_notification_url,
      threeDsServerTransactionId: raw.threeds.threeds_server_transaction_id,
      protocolVersion: raw.threeds.threeds_protocol_version ?? raw.threeds.message_version,
    },
  };
}
