// src/services/apiClient.js

/**
 * Centralized API client
 *
 * Auth contract:
 *  - POST /api/auth/session            → Entra JWT (handled by AuthProvider)
 *  - Gateway + provider calls          → session token  (Bearer <sessionToken>)
 *  - Management / staff-plane calls    → Entra JWT      (Bearer <entraToken>)
 *  - Registration health check         → anonymous (no auth header needed)
 *
 * Token selection is automatic — callers always supply both tokens via authConfig;
 * the client picks the correct one based on the target baseUrl.
 *
 * Routing:
 *  - Services with explicit endpoint   → call baseUrl+endpoint directly
 *  - Provider ops (google, creative…)  → POST /api/execution/request
 */

import { getService } from './serviceCatalog';
import { API_BASE, MGMT_BASE } from '../auth/authConfig';

// Domains that require a staff Entra JWT rather than a Gateway session token
const STAFF_PLANE_ORIGINS = [MGMT_BASE].map(u => new URL(u).origin);

function isStaffPlane(baseUrl) {
  if (!baseUrl) return false;
  try {
    return STAFF_PLANE_ORIGINS.includes(new URL(baseUrl).origin);
  } catch {
    return false;
  }
}

export async function callApi(providerId, serviceId, inputJson, authConfig) {
  const { sessionToken, entraToken, tenantId } = authConfig || {};

  // Look up the service definition from catalog
  const serviceDef = getService(providerId, serviceId);
  if (!serviceDef) {
    throw new Error(`Unknown service: ${providerId}/${serviceId}`);
  }

  // Parse the input payload
  let payload;
  try {
    payload = typeof inputJson === 'string' ? JSON.parse(inputJson) : inputJson;
  } catch (e) {
    throw new Error(`Invalid JSON payload: ${e.message}`);
  }

  // Build headers
  const headers = { 'Content-Type': 'application/json' };

  // Merge any custom headers from service definition (e.g. function key)
  if (serviceDef.headers) {
    Object.assign(headers, serviceDef.headers);
  }

  // ---- ROUTING ----

  // Direct calls (have explicit endpoint)
  if (serviceDef.endpoint) {
    if (serviceDef.endpoint.startsWith('/api/auth/session')) {
      // Session exchange — always Entra JWT
      if (!entraToken) throw new Error('entraToken required for /api/auth/session');
      headers['Authorization'] = `Bearer ${entraToken}`;
    } else if (isStaffPlane(serviceDef.baseUrl)) {
      // Management API — staff Entra JWT
      if (entraToken) headers['Authorization'] = `Bearer ${entraToken}`;
    } else {
      // Gateway, Registration, or anything else — session token (or anonymous if none)
      if (sessionToken) headers['Authorization'] = `Bearer ${sessionToken}`;
    }

    // Handle URL templating: replace {param} with values from payload
    let endpoint = serviceDef.endpoint;
    const urlParams = endpoint.match(/\{(\w+)\}/g) || [];
    for (const param of urlParams) {
      const paramName = param.slice(1, -1);
      const value = payload[paramName];
      if (value !== undefined) {
        endpoint = endpoint.replace(param, encodeURIComponent(value));
        delete payload[paramName];
      }
    }

    // For GET/DELETE, convert remaining payload to query string
    let url = `${serviceDef.baseUrl || API_BASE}${endpoint}`;
    if ((serviceDef.method === 'GET' || serviceDef.method === 'DELETE') && Object.keys(payload).length > 0) {
      const queryParams = new URLSearchParams();
      for (const [key, value] of Object.entries(payload)) {
        if (value !== undefined && value !== null) {
          queryParams.append(key, value);
        }
      }
      const qs = queryParams.toString();
      if (qs) url += `?${qs}`;
    }

    const resp = await fetch(url, {
      method: serviceDef.method || 'GET',
      headers,
      body: (serviceDef.method !== 'GET' && serviceDef.method !== 'DELETE')
        ? JSON.stringify(payload)
        : undefined
    });

    return handleResponse(resp);
  }

  // Provider execution calls → POST /api/execution/request
  if (!sessionToken) {
    throw new Error('sessionToken required for provider API calls');
  }

  headers['Authorization'] = `Bearer ${sessionToken}`;
  headers['X-Requested-With'] = 'AdPlatform-Client';

  const execBody = {
    provider: providerId,
    operation: serviceId,
    tenantId: tenantId || undefined,
    payload
  };

  const resp = await fetch(`${API_BASE}/api/execution/request`, {
    method: 'POST',
    headers,
    body: JSON.stringify(execBody)
  });

  return handleResponse(resp);
}

async function handleResponse(resp) {
  if (resp.status === 401) {
    const text = await resp.text();
    throw new Error(`API 401: ${text}`);
  }

  if (!resp.ok) {
    const text = await resp.text();
    throw new Error(`API ${resp.status}: ${text}`);
  }

  return resp.json();
}
