import type { IntegrationCredentials } from "@/lib/services/integration-credentials";
import { getValidAccessToken, forceRefreshAccessToken } from "@/lib/services/integration-credentials";

/**
 * Every sync service must implement this interface.
 * For now these return mock data — replace with real API calls later.
 *
 * Credentials are passed to connect/sync so the service can use
 * stored tokens without managing storage itself.
 */
export interface SyncService {
  /** Unique ID matching the integration catalog (e.g. "gsc", "ga4") */
  id: string;

  /** Human-readable name */
  name: string;

  /** Connect the integration. Receives credentials if user provides them. */
  connect(brandId: string, creds?: IntegrationCredentials): Promise<SyncResult>;

  /** Disconnect and clean up */
  disconnect(brandId: string): Promise<SyncResult>;

  /**
   * Pull fresh data. Receives stored credentials.
   *
   * When `options.force` is true, the service should perform a full re-sync
   * from scratch — deleting any cached state and pulling the full historical
   * window. Without force, services are free to do incremental syncs.
   */
  sync(
    brandId: string,
    creds?: IntegrationCredentials,
    options?: SyncOptions,
  ): Promise<SyncResult>;
}

export interface SyncOptions {
  /** Force a full re-sync (delete existing data, pull full history window) */
  force?: boolean;
}

export interface SyncResult {
  success: boolean;
  message: string;
  /** Credentials to store (e.g. refreshed tokens from OAuth) */
  credentials?: IntegrationCredentials;
  data?: Record<string, unknown>;
}

/**
 * Authenticated API call for Google OAuth-backed integrations.
 *
 * Handles the full token lifecycle so integration services never touch
 * tokens directly:
 *   1. Calls `getValidAccessToken` to proactively refresh any token
 *      that's expired or expiring within 5 minutes.
 *   2. If the API still returns 401 (rare — Google sometimes revokes
 *      tokens before their stated expiry), force-refresh the token
 *      once and retry the request.
 *   3. Only if the second attempt also fails auth do we surface the
 *      401 back to the caller. At that point the refresh_token itself
 *      is likely revoked and the UI should prompt for reconnect.
 *
 * Callers should NOT set `bearerToken` — this helper owns auth.
 */
export async function apiCallAuthed(
  url: string,
  options: {
    method?: string;
    headers?: Record<string, string>;
    body?: unknown;
  },
  auth: { brandId: string; integrationId: string },
): Promise<{ ok: boolean; status: number; data: unknown; error?: string }> {
  const token = await getValidAccessToken(auth.brandId, auth.integrationId);
  const first = await apiCall(url, { ...options, bearerToken: token });
  if (first.ok) return first;
  if (first.status !== 401) {
    // Log non-auth failures too so stale-data reports are diagnosable.
    console.info("[apiCallAuthed] non-401 failure", {
      integrationId: auth.integrationId,
      status: first.status,
      error: first.error,
    });
    return first;
  }
  console.info("[apiCallAuthed] 401 — forcing refresh + retry", {
    integrationId: auth.integrationId,
  });

  // One-shot recovery: token appeared valid but Google says no. Force a
  // refresh and retry — handles mid-flight expiry and early revocations.
  try {
    const fresh = await forceRefreshAccessToken(auth.brandId, auth.integrationId);
    return await apiCall(url, { ...options, bearerToken: fresh });
  } catch {
    // Refresh itself failed. refreshGoogleAccessToken has already marked
    // the integration as "expired". Surface the original 401 so the
    // caller can decide how to present it.
    return first;
  }
}

/**
 * Helper: make an authenticated API call.
 * Reusable across all integration services.
 */
export async function apiCall(
  url: string,
  options: {
    method?: string;
    headers?: Record<string, string>;
    body?: unknown;
    apiKey?: string;
    bearerToken?: string;
  } = {}
): Promise<{ ok: boolean; status: number; data: unknown; error?: string }> {
  const headers: Record<string, string> = {
    "Content-Type": "application/json",
    ...options.headers,
  };

  if (options.bearerToken) {
    headers["Authorization"] = `Bearer ${options.bearerToken}`;
  }
  if (options.apiKey) {
    // Some APIs use query params for API keys — but most use headers
    headers["X-Api-Key"] = options.apiKey;
  }

  try {
    const res = await fetch(url, {
      method: options.method ?? "GET",
      headers,
      body: options.body ? JSON.stringify(options.body) : undefined,
    });

    const data = await res.json().catch(() => ({}));

    if (!res.ok) {
      return {
        ok: false,
        status: res.status,
        data,
        error: (data as Record<string, unknown>)?.error?.toString() ?? `HTTP ${res.status}`,
      };
    }

    return { ok: true, status: res.status, data };
  } catch (err) {
    return {
      ok: false,
      status: 0,
      data: null,
      error: err instanceof Error ? err.message : "Network error",
    };
  }
}
