/**
 * Firebase "create from scratch" — automate project creation for beginners.
 *
 * Flow:
 *   1. Create GCP project (gcloud projects create)
 *   2. Link billing account
 *   3. Enable required APIs (incl. firebase.googleapis.com) — must be before addFirebase
 *   4. Add Firebase to project (Firebase Management API)
 *   5. Create Android, iOS and Web apps in Firebase
 *   6. Extract SHA-1 from debug.keystore
 *   7. Add SHA-1 to Android app (Firebase Management API)
 *
 * Requires: firebase login (or gcloud auth login), gcloud CLI.
 */

const { exec } = require('node:child_process');
const { promisify } = require('node:util');
const path = require('node:path');
const fs = require('fs-extra');
const os = require('node:os');

const execAsync = promisify(exec);

function sleep(ms) {
  return new Promise((r) => setTimeout(r, ms));
}

const DEBUG_KEYSTORE = path.join(os.homedir(), '.android', 'debug.keystore');
const KASY_KEYS_DIR = path.join(os.homedir(), '.kasy', 'keys');

/** Returns a persistent path for the firebase service account key (survives reboots). */
function getServiceAccountKeyPath(projectId) {
  return path.join(KASY_KEYS_DIR, `firebase_key_${projectId}.json`);
}
const REQUIRED_APIS = [
  'compute.googleapis.com',            // Compute Engine (required for Compute SA, used by Cloud Run/Functions)
  'secretmanager.googleapis.com',
  'storage.googleapis.com',
  'firebase.googleapis.com',
  'firebasestorage.googleapis.com',    // Firebase Storage (default bucket)
  'firestore.googleapis.com',          // Firestore (required for firestore:rules deploy)
  'identitytoolkit.googleapis.com',    // Firebase Auth
  'cloudfunctions.googleapis.com',     // Cloud Functions (required for functions deploy)
  'cloudbuild.googleapis.com',         // Cloud Build (required by Cloud Functions v2)
  'run.googleapis.com',                // Cloud Run (required by Cloud Functions v2)
  'artifactregistry.googleapis.com',   // Artifact Registry (required by Cloud Functions v2)
  'eventarc.googleapis.com',           // Eventarc (required for Cloud Functions v2 event triggers)
  'logging.googleapis.com',            // Cloud Logging (Cloud Build writes build logs here)
];

async function run(cmd, cwd = process.cwd()) {
  try {
    const { stdout, stderr } = await execAsync(cmd, {
      cwd,
      maxBuffer: 10 * 1024 * 1024,
      encoding: 'utf8',
    });
    return { ok: true, stdout: (stdout || '').trim(), stderr: (stderr || '').trim() };
  } catch (err) {
    return {
      ok: false,
      error: err.message,
      stdout: (err.stdout || '').trim(),
      stderr: (err.stderr || '').trim(),
    };
  }
}

/**
 * Generate a valid GCP project ID from app name.
 * Format: 6-30 chars, lowercase, numbers, hyphens. Must be globally unique.
 */
function generateProjectId(appName) {
  const slug = appName
    .normalize('NFD')
    .replace(/[\u0300-\u036f]/g, '')
    .toLowerCase()
    .replace(/[^a-z0-9]/g, '-')
    .replace(/-+/g, '-')
    .replace(/^-|-$/g, '');
  const base = (slug || 'app').slice(0, 20);
  const suffix = Date.now().toString(36).slice(-6);
  return `${base}-${suffix}`;
}

/**
 * Check if gcloud is installed and user is logged in.
 */
/**
 * Check if gcloud is installed and logged in.
 * Works on Windows, macOS and Linux — gcloud CLI is cross-platform.
 */
async function checkGcloudAuth() {
  const version = await run('gcloud --version');
  if (!version.ok) return { ok: false, error: version.error, missing: 'gcloud' };

  const auth = await run('gcloud auth print-access-token');
  if (!auth.ok) return { ok: false, error: 'Not logged in. Run: gcloud auth login', missing: 'auth' };

  return { ok: true };
}

/**
 * Get platform-specific install instructions for gcloud CLI.
 */
function getGcloudInstallInstructions() {
  const platform = process.platform;
  if (platform === 'darwin') {
    return { install: 'brew install --cask google-cloud-sdk', after: 'gcloud auth login', url: 'https://cloud.google.com/sdk/docs/install' };
  }
  if (platform === 'win32') {
    return { install: null, hint: 'Download and run the installer from the link below, then restart the terminal.', after: 'gcloud auth login', url: 'https://cloud.google.com/sdk/docs/install#windows' };
  }
  return { install: 'curl https://sdk.cloud.google.com | bash', after: 'gcloud auth login', url: 'https://cloud.google.com/sdk/docs/install' };
}

/**
 * List billing accounts the user has access to (open/active only).
 * @returns {{ ok: boolean, accounts?: Array<{ id: string, name: string }>, error?: string }}
 */
async function listBillingAccounts() {
  const result = await run('gcloud billing accounts list --filter=open=true --format=json');
  if (!result.ok) return { ok: false, error: result.stderr || result.error };
  try {
    const data = JSON.parse(result.stdout || '[]');
    const accounts = (Array.isArray(data) ? data : []).map((a) => ({
      id: (a.name || '').replace('billingAccounts/', ''),
      name: a.displayName || a.name || '',
    })).filter((a) => a.id);
    return { ok: true, accounts };
  } catch (e) {
    return { ok: false, error: `Failed to parse billing accounts: ${e.message}` };
  }
}

/**
 * List GCP organizations the user has access to.
 * Returns an empty list (not an error) for personal accounts with no organization.
 * @returns {{ ok: boolean, organizations?: Array<{ id: string, name: string }>, error?: string }}
 */
async function listGcpOrganizations() {
  const result = await run('gcloud organizations list --format=json');
  if (!result.ok) return { ok: true, organizations: [] }; // non-fatal: personal accounts return an error here
  try {
    const data = JSON.parse(result.stdout || '[]');
    const organizations = (Array.isArray(data) ? data : []).map((o) => ({
      id: (o.name || '').replace('organizations/', ''),
      name: o.displayName || o.name || '',
    })).filter((o) => o.id);
    return { ok: true, organizations };
  } catch (e) {
    return { ok: true, organizations: [] }; // parse failure treated as no org
  }
}

/**
 * Detect if billing error is a QuotaFailure (e.g. too many projects linked to billing).
 */
function isBillingQuotaError(error) {
  const s = String(error || '');
  return /QuotaFailure|billing quota exceeded|quota exceeded/i.test(s);
}

/**
 * Link a billing account to a project (required for Blaze/Cloud Functions).
 */
async function linkBillingAccount(projectId, billingAccountId) {
  const result = await run(
    `gcloud billing projects link ${projectId} --billing-account=${billingAccountId}`
  );
  if (!result.ok) {
    return { ok: false, error: result.stderr || result.error };
  }
  return { ok: true };
}

/**
 * Get OAuth access token for Firebase Management API.
 */
async function getAccessToken() {
  const result = await run('gcloud auth print-access-token');
  if (!result.ok) throw new Error(result.error || 'Failed to get access token');
  return result.stdout;
}

/**
 * Create GCP project.
 * GCP requires display_name >= 4 chars. Pads short names.
 */
async function createGcpProject(projectId, projectName, options = {}) {
  const { organizationId } = options;
  const displayName = (projectName || projectId).trim().length >= 4
    ? projectName
    : `${projectName || projectId} app`;
  const orgFlag = organizationId ? ` --organization=${organizationId}` : '';
  const cmd = `gcloud projects create ${projectId} --name="${displayName.replace(/"/g, '\\"')}"${orgFlag}`;
  const result = await run(cmd);
  if (!result.ok) {
    if (result.stderr?.includes('already exists') || result.error?.includes('already exists')) {
      return { ok: true, projectId, existed: true };
    }
    return { ok: false, error: result.stderr || result.error };
  }
  return { ok: true, projectId, existed: false };
}

/**
 * Add Firebase via REST API (uses gcloud token).
 */
async function addFirebaseToProjectRest(projectId) {
  const token = await getAccessToken();
  const url = `https://firebase.googleapis.com/v1beta1/projects/${projectId}:addFirebase`;
  const res = await fetch(url, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${token}`,
      'Content-Type': 'application/json',
      'X-Goog-User-Project': projectId,
    },
    body: JSON.stringify({}),
  });
  if (!res.ok) {
    const text = await res.text();
    if (res.status === 409 || text.includes('ALREADY_EXISTS')) return { ok: true, existed: true };
    return { ok: false, error: `${res.status}: ${text}` };
  }
  return { ok: true, existed: false };
}

/**
 * Add Firebase to existing GCP project.
 * Tries Firebase CLI first (handles propagation); falls back to REST (gcloud auth).
 * Retries on 404 (project not yet propagated) with delay.
 */
async function addFirebaseToProject(projectId, options = {}) {
  const { onProgress = () => {}, maxRetries = 3 } = options;
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    let result = await run(`firebase projects:addfirebase ${projectId} --non-interactive`);
    if (result.ok) return { ok: true, existed: false };
    if (result.stderr?.includes('ALREADY_EXISTS') || result.stderr?.includes('already has Firebase')) {
      return { ok: true, existed: true };
    }
    const is404 = result.stderr?.includes('404') || result.stderr?.includes('not found');
    const is400 = result.stderr?.includes('400') || result.stderr?.includes('INVALID_ARGUMENT') || result.stderr?.includes('invalid argument');
    const isAuthError = result.stderr?.includes('login') || result.stderr?.includes('auth') || result.stderr?.includes('Must be logged in');
    if (isAuthError || is400) {
      const restResult = await addFirebaseToProjectRest(projectId);
      if (restResult.ok) return restResult;
      if (is400) return { ok: false, error: restResult.error };
      if (isAuthError) return { ok: false, error: restResult.error };
    }
    if (is404 && attempt < maxRetries) {
      onProgress('wait-propagate');
      await sleep(15000);
      continue;
    }
    return { ok: false, error: result.stderr || result.error };
  }
  return { ok: false, error: 'Max retries exceeded' };
}

/**
 * Create Firestore database (required for firestore:rules deploy).
 * Uses gcloud firestore databases create. Retries up to 3x (API propagation).
 */
async function createFirestoreDatabase(projectId, location = 'us-central1') {
  const maxAttempts = 3;
  const delayMs = 15000;
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    const result = await run(
      `gcloud firestore databases create --location=${location} --project=${projectId} --quiet`
    );
    if (result.ok) return { ok: true, existed: false };
    if (result.stderr?.includes('ALREADY_EXISTS') || result.stderr?.includes('already exists')) {
      return { ok: true, existed: true };
    }
    if (attempt < maxAttempts) {
      await sleep(delayMs);
    } else {
      return { ok: false, error: result.stderr || result.error };
    }
  }
  return { ok: false, error: 'Max retries exceeded' };
}

/**
 * Create Firebase Storage default bucket (avoids manual "Get Started" in Console).
 * Requires Blaze plan. Uses firebasestorage.googleapis.com REST API. Retries up to 3x.
 * As of Oct 2024, the API requires location in the request body.
 */
async function createFirebaseStorageBucket(projectId, location = 'us-central1') {
  const maxAttempts = 3;
  const delayMs = 15000;
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    const token = await getAccessToken();
    const url = `https://firebasestorage.googleapis.com/v1alpha/projects/${projectId}/defaultBucket`;
    const res = await fetch(url, {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${token}`,
        'Content-Type': 'application/json',
        'X-Goog-User-Project': projectId,
      },
      body: JSON.stringify({ location }),
    });
    if (res.ok) return { ok: true, existed: false };
    const text = await res.text();
    if (res.status === 409 || text.includes('ALREADY_EXISTS') || text.includes('already exists')) {
      return { ok: true, existed: true };
    }
    if (attempt < maxAttempts) {
      await sleep(delayMs);
    } else {
      return { ok: false, error: `${res.status}: ${text}` };
    }
  }
  return { ok: false, error: 'Max retries exceeded' };
}

/**
 * Enable required Google Cloud APIs.
 */
async function enableApis(projectId) {
  const apis = REQUIRED_APIS.join(' ');
  const result = await run(`gcloud services enable ${apis} --project=${projectId}`);
  if (!result.ok) {
    return { ok: false, error: result.stderr || result.error };
  }
  return { ok: true };
}

/**
 * Poll a Firebase long-running operation until it completes.
 * Returns { ok: true, response } or { ok: false, error }.
 */
async function pollOperation(operationName, token, { maxAttempts = 20, delayMs = 3000, projectId } = {}) {
  // Extract projectId from operation name if not passed explicitly (e.g. "projects/my-proj/operations/abc")
  const quotaProject = projectId || (operationName.startsWith('projects/') ? operationName.split('/')[1] : null);
  for (let i = 0; i < maxAttempts; i++) {
    const headers = { Authorization: `Bearer ${token}` };
    if (quotaProject) headers['X-Goog-User-Project'] = quotaProject;
    const res = await fetch(`https://firebase.googleapis.com/v1beta1/${operationName}`, { headers });
    if (!res.ok) {
      const text = await res.text();
      return { ok: false, error: `${res.status}: ${text}` };
    }
    const data = await res.json();
    if (data.done) {
      if (data.error) return { ok: false, error: data.error.message || JSON.stringify(data.error) };
      return { ok: true, response: data.response };
    }
    await sleep(delayMs);
  }
  return { ok: false, error: 'Operation timed out waiting for Firebase app creation' };
}

/**
 * Create Android app in Firebase project.
 */
async function createAndroidApp(projectId, packageName, appName) {
  const token = await getAccessToken();
  const url = `https://firebase.googleapis.com/v1beta1/projects/${projectId}/androidApps`;
  const res = await fetch(url, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${token}`,
      'Content-Type': 'application/json',
      'X-Goog-User-Project': projectId,
    },
    body: JSON.stringify({
      packageName,
      displayName: appName || packageName,
    }),
  });

  if (!res.ok) {
    const text = await res.text();
    return { ok: false, error: `${res.status}: ${text}` };
  }

  const operation = await res.json();
  // Firebase returns a long-running operation — poll until done to get the real appId
  const pollResult = await pollOperation(operation.name, token, { projectId });
  if (!pollResult.ok) return { ok: false, error: pollResult.error };
  const app = pollResult.response;
  return { ok: true, appId: app.appId, fullName: app.name };
}

/**
 * Create iOS app in Firebase project.
 */
async function createIosApp(projectId, bundleId, appName) {
  const token = await getAccessToken();
  const url = `https://firebase.googleapis.com/v1beta1/projects/${projectId}/iosApps`;
  const res = await fetch(url, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${token}`,
      'Content-Type': 'application/json',
      'X-Goog-User-Project': projectId,
    },
    body: JSON.stringify({
      bundleId,
      displayName: appName || bundleId,
    }),
  });

  if (!res.ok) {
    const text = await res.text();
    return { ok: false, error: `${res.status}: ${text}` };
  }

  const operation = await res.json();
  const pollResult = await pollOperation(operation.name, token, { projectId });
  if (!pollResult.ok) return { ok: false, error: pollResult.error };
  const app = pollResult.response;
  return { ok: true, appId: app.appId, fullName: app.name };
}

/**
 * Create Web app in Firebase project.
 */
async function createWebApp(projectId, appName) {
  const token = await getAccessToken();
  const url = `https://firebase.googleapis.com/v1beta1/projects/${projectId}/webApps`;
  const res = await fetch(url, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${token}`,
      'Content-Type': 'application/json',
      'X-Goog-User-Project': projectId,
    },
    body: JSON.stringify({
      displayName: appName || 'Web app',
    }),
  });

  if (!res.ok) {
    const text = await res.text();
    return { ok: false, error: `${res.status}: ${text}` };
  }

  const operation = await res.json();
  const pollResult = await pollOperation(operation.name, token, { projectId });
  if (!pollResult.ok) return { ok: false, error: pollResult.error };
  const app = pollResult.response;
  return { ok: true, appId: app.appId, fullName: app.name };
}

/**
 * Ensure debug.keystore exists. Creates it if missing (standard Android debug key).
 */
async function ensureDebugKeystore() {
  const androidDir = path.dirname(DEBUG_KEYSTORE);
  if (!(await fs.pathExists(DEBUG_KEYSTORE))) {
    await fs.ensureDir(androidDir);
    const createResult = await run(
      `keytool -genkeypair -v -keystore "${DEBUG_KEYSTORE}" -storepass android -alias androiddebugkey -keypass android -keyalg RSA -keysize 2048 -validity 10000 -dname "CN=Android Debug,O=Android,C=US" -noprompt`
    );
    if (!createResult.ok) return { ok: false, error: createResult.stderr || createResult.error };
  }
  return { ok: true };
}

/**
 * Extract SHA-1 from debug.keystore.
 * Creates debug.keystore first if it doesn't exist.
 */
async function extractSha1() {
  const ensureResult = await ensureDebugKeystore();
  if (!ensureResult.ok) return ensureResult;

  const result = await run(
    `keytool -list -v -keystore "${DEBUG_KEYSTORE}" -alias androiddebugkey -storepass android -keypass android`
  );
  if (!result.ok) return { ok: false, error: result.stderr || result.error };

  const output = (result.stdout || '') + '\n' + (result.stderr || '');
  const sha1Match = output.match(/SHA-?1[:\s]+([A-Fa-f0-9:]+)/i);
  const sha1 = sha1Match?.[1]?.trim().replace(/\s+/g, '');
  if (!sha1 || !/^[A-Fa-f0-9:]+$/.test(sha1) || sha1.replace(/:/g, '').length !== 40) {
    return { ok: false, error: 'Could not parse SHA1 from keytool output' };
  }

  return { ok: true, sha1 };
}

/**
 * Add SHA-1 certificate to Android app.
 * Uses Firebase CLI first, falls back to REST API. Retries up to 3 times (project propagation).
 */
async function addSha1ToApp(projectId, androidAppId, sha1) {
  if (!sha1 || typeof sha1 !== 'string') {
    return { ok: false, error: 'SHA-1 hash is missing. Run the app once in Android Studio to create debug.keystore.' };
  }
  const maxAttempts = 3;
  const delayMs = 10000;
  let lastError = null;

  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    const result = await run(
      `firebase apps:android:sha:create ${androidAppId} "${sha1}" --project=${projectId} --non-interactive`
    );
    if (result.ok) return { ok: true, existed: false };
    if (result.stderr?.includes('ALREADY_EXISTS') || result.stderr?.includes('already exists')) {
      return { ok: true, existed: true };
    }
    lastError = result.stderr || result.error;

    const restResult = await addSha1ToAppRest(projectId, androidAppId, sha1);
    if (restResult.ok) return restResult;
    lastError = restResult.error;

    if (attempt < maxAttempts) {
      await sleep(delayMs);
    }
  }
  return { ok: false, error: lastError };
}

async function addSha1ToAppRest(projectId, androidAppId, sha1) {
  const token = await getAccessToken();
  const parent = `projects/${projectId}/androidApps/${androidAppId}`;
  const url = `https://firebase.googleapis.com/v1beta1/${parent}/sha`;
  // REST API expects SHA-1 without colons, 40 hex chars lowercase
  const shaHash = sha1.replace(/:/g, '').toLowerCase();
  if (shaHash.length !== 40 || !/^[a-f0-9]+$/.test(shaHash)) {
    return { ok: false, error: `Invalid SHA-1 format: expected 40 hex chars, got ${shaHash.length}` };
  }
  const res = await fetch(url, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${token}`,
      'Content-Type': 'application/json',
      'X-Goog-User-Project': projectId,
    },
    body: JSON.stringify({
      shaHash,
      certType: 'SHA_1',
    }),
  });
  if (!res.ok) {
    const text = await res.text();
    if (res.status === 409 || text.includes('ALREADY_EXISTS')) return { ok: true, existed: true };
    let errMsg = `${res.status}: ${text}`;
    try {
      const json = JSON.parse(text);
      if (json?.error?.message) errMsg = `${res.status}: ${json.error.message}`;
    } catch (_) {}
    return { ok: false, error: errMsg };
  }
  return { ok: true, existed: false };
}

/**
 * Create service account and download key for deploy.
 */
async function createServiceAccountAndKey(projectId, outputPath) {
  const saName = 'kasy-deploy';
  const saEmail = `${saName}@${projectId}.iam.gserviceaccount.com`;

  const createSa = await run(
    `gcloud iam service-accounts create ${saName} --display-name="Kasy Deploy" --project=${projectId}`
  );
  if (!createSa.ok && !createSa.stderr?.includes('already exists')) {
    return { ok: false, error: createSa.stderr || createSa.error };
  }

  const roles = [
    'roles/editor',
    'roles/firebase.admin',          // Firebase-specific deploy operations
    'roles/iam.serviceAccountUser',  // Required for Cloud Functions deployment
  ];
  for (const role of roles) {
    const bind = await run(
      `gcloud projects add-iam-policy-binding ${projectId} --member="serviceAccount:${saEmail}" --role="${role}" --quiet`
    );
    if (!bind.ok) {
      return { ok: false, error: `Failed to add role ${role}: ${bind.stderr || bind.error}` };
    }
  }

  const keyResult = await run(
    `gcloud iam service-accounts keys create "${outputPath}" --iam-account=${saEmail} --project=${projectId}`
  );
  if (!keyResult.ok) return { ok: false, error: keyResult.stderr || keyResult.error };

  return { ok: true, keyPath: outputPath };
}

/**
 * Check if billing (Blaze plan) is enabled for a GCP project.
 * @param {string} projectId
 * @returns {{ ok: boolean, enabled?: boolean, error?: string }}
 */
async function checkBillingEnabled(projectId) {
  const result = await run(`gcloud billing projects describe ${projectId} --format="value(billingEnabled)"`);
  if (!result.ok) return { ok: false, error: result.stderr || result.error };
  return { ok: true, enabled: result.stdout.trim().toLowerCase() === 'true' };
}

/**
 * Enable Firebase Auth sign-in providers: Email/Password, Anonymous and Google.
 * Uses the Identity Toolkit Admin v2 REST API with gcloud credentials.
 *
 * Flow:
 *  1. Call identityPlatform:initializeAuth to create the auth config for the project
 *     (required for new projects — PATCH fails with CONFIGURATION_NOT_FOUND without it).
 *  2. PATCH /config to enable Email/Password and Anonymous.
 *  3. POST defaultSupportedIdpConfigs to enable Google Sign-In.
 *     Google Sign-In requires an OAuth client_id that Firebase creates when the user
 *     enables it in the Console — if the client doesn't exist yet this step is skipped
 *     and googleSignInSkipped=true is returned so the caller can show a targeted hint.
 *
 * Non-fatal: returns { ok: false, error } without throwing if the API call fails.
 *
 * @param {string} projectId
 * @returns {{ ok: boolean, googleSignInSkipped?: boolean, error?: string }}
 */
async function enableAuthProviders(projectId, { maxRetries = 3, retryDelayMs = 15000 } = {}) {
  let token;
  try {
    token = await getAccessToken();
  } catch (_) {
    return { ok: false, error: 'Could not get access token' };
  }

  // Step 1: Initialize Firebase Auth for the project.
  // New projects have no auth config — PATCH returns CONFIGURATION_NOT_FOUND without this.
  // The endpoint is idempotent: it returns {} on success or if already initialized.
  const initUrl = `https://identitytoolkit.googleapis.com/v2/projects/${projectId}/identityPlatform:initializeAuth`;
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    const initRes = await fetch(initUrl, {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${token}`,
        'Content-Type': 'application/json',
        'X-Goog-User-Project': projectId,
      },
      body: JSON.stringify({}),
    });
    if (initRes.ok) break;
    await initRes.text(); // consume body to release connection
    if (attempt < maxRetries && (initRes.status === 404 || initRes.status === 503)) {
      await sleep(retryDelayMs);
      try { token = await getAccessToken(); } catch (_) {}
      continue;
    }
    // Non-fatal — still attempt PATCH below in case auth was already initialized.
    break;
  }

  // Step 2: Enable Email/Password and Anonymous auth providers.
  const configUrl = `https://identitytoolkit.googleapis.com/admin/v2/projects/${projectId}/config?updateMask=signIn.email,signIn.anonymous`;
  let lastError;
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try { token = await getAccessToken(); } catch (_) {
      return { ok: false, error: 'Could not get access token' };
    }
    const res = await fetch(configUrl, {
      method: 'PATCH',
      headers: {
        Authorization: `Bearer ${token}`,
        'Content-Type': 'application/json',
        'X-Goog-User-Project': projectId,
      },
      body: JSON.stringify({
        signIn: {
          email: { enabled: true, passwordRequired: true },
          anonymous: { enabled: true },
        },
      }),
    });
    if (res.ok) {
      // Step 3: Enable Google Sign-In via defaultSupportedIdpConfigs.
      // This requires an OAuth client_id. Firebase creates one automatically when the user
      // enables Google Sign-In in the Console. If the client doesn't exist yet, the POST
      // returns 400 INVALID_CONFIG — skip and return googleSignInSkipped=true.
      const googleUrl = `https://identitytoolkit.googleapis.com/admin/v2/projects/${projectId}/defaultSupportedIdpConfigs?idpId=google.com`;
      const googleRes = await fetch(googleUrl, {
        method: 'POST',
        headers: {
          Authorization: `Bearer ${token}`,
          'Content-Type': 'application/json',
          'X-Goog-User-Project': projectId,
        },
        body: JSON.stringify({
          name: `projects/${projectId}/defaultSupportedIdpConfigs/google.com`,
          enabled: true,
        }),
      });
      if (!googleRes.ok) {
        const googleText = await googleRes.text();
        // 409 = already exists — update it to ensure it's enabled.
        if (googleRes.status === 409 || googleText.includes('ALREADY_EXISTS')) {
          const patchUrl = `https://identitytoolkit.googleapis.com/admin/v2/projects/${projectId}/defaultSupportedIdpConfigs/google.com?updateMask=enabled`;
          await fetch(patchUrl, {
            method: 'PATCH',
            headers: {
              Authorization: `Bearer ${token}`,
              'Content-Type': 'application/json',
              'X-Goog-User-Project': projectId,
            },
            body: JSON.stringify({ enabled: true }),
          });
          return { ok: true };
        }
        // 400 INVALID_CONFIG (client_id empty) = no OAuth client created yet.
        // Email/Password and Anonymous are already enabled — just skip Google.
        return { ok: true, googleSignInSkipped: true };
      }
      return { ok: true };
    }
    const text = await res.text();
    lastError = `${res.status}: ${text}`;
    // Retry on 404 (API not yet active) or 503 (transient). Fatal on 400/403.
    if (attempt < maxRetries && (res.status === 404 || res.status === 503)) {
      await sleep(retryDelayMs);
      continue;
    }
    break;
  }
  return { ok: false, error: lastError };
}

/**
 * Full setup from scratch.
 * @param {object} options.resumeFromBilling - { projectId } to skip create and resume from billing
 * @returns {{ ok: boolean, projectId?: string, error?: string, billingFailed?: boolean }}
 */
async function setupFromScratch(appName, bundleId, options = {}) {
  const { onProgress = () => {}, includeWeb = true, region = 'us-central1', tr, resumeFromBilling, billingAccountId: preferredBillingId, organizationId } = options;
  const projectId = resumeFromBilling?.projectId || generateProjectId(appName);

  const authCheck = await checkGcloudAuth();
  if (!authCheck.ok) return { ok: false, error: authCheck.error };

  if (!resumeFromBilling) {
    onProgress('gcp-project');
    const createResult = await createGcpProject(projectId, appName, { organizationId });
    if (!createResult.ok) return { ok: false, error: createResult.error };
  }

  onProgress('billing');
  const billingList = await listBillingAccounts();
  const hasAccounts = billingList.ok && billingList.accounts?.length > 0;
  if (!hasAccounts && !preferredBillingId) {
    return {
      ok: false,
      error: `Project ${projectId} was created but no billing account found. Create one at https://console.cloud.google.com/billing and link it. Then use "Use existing project" with ID: ${projectId}`,
      projectId,
    };
  }
  const billingAccountId = preferredBillingId || billingList.accounts[0].id;
  const linkResult = await linkBillingAccount(projectId, billingAccountId);
  if (!linkResult.ok) {
    // Manage URL: remove projects from billing to free quota, then retry
    const manageLink = `https://console.cloud.google.com/billing/${billingAccountId}/manage?project=${projectId}`;
    const rawError = linkResult.error;
    const isQuota = isBillingQuotaError(rawError);
    const shortError = isQuota
      ? 'Cloud billing quota exceeded (too many projects linked to this billing account)'
      : rawError;
    return {
      ok: false,
      error: `Project created but billing link failed: ${shortError}. Manage projects: ${manageLink}`,
      projectId,
      billingFailed: true,
      billingManualLink: manageLink,
    };
  }

  onProgress('enable-apis');
  const apisResult = await enableApis(projectId);
  if (!apisResult.ok) return { ok: false, error: apisResult.error };

  // Set ADC quota project so Firebase REST API calls (createAndroidApp etc.) work with user credentials.
  // Non-fatal when ADC has not been initialized (gcloud auth application-default login not run):
  // in that case the user token from gcloud auth print-access-token + X-Goog-User-Project header is sufficient.
  const quotaResult = await run(`gcloud auth application-default set-quota-project ${projectId}`);
  if (!quotaResult.ok) {
    const adcNotSetup = (quotaResult.error + quotaResult.stderr).includes('Application default credentials have not been set up');
    if (!adcNotSetup) {
      return { ok: false, error: `Failed to set ADC quota project: ${quotaResult.error}`, projectId };
    }
    // ADC not configured — continue; user token + X-Goog-User-Project header covers quota.
  }

  onProgress('wait-propagate');
  await sleep(20000);

  onProgress('add-firebase');
  const addResult = await addFirebaseToProject(projectId, { onProgress });
  if (!addResult.ok) return { ok: false, error: `[addFirebase] ${addResult.error}`, projectId };

  // Enable Email/Password, Anonymous and Google Sign-In auth providers automatically.
  // Non-fatal — project setup continues even if this call fails.
  const authResult = await enableAuthProviders(projectId);
  if (!authResult.ok) {
    onProgress('auth-providers-warn', {
      error: authResult.error,
      url: `https://console.firebase.google.com/project/${projectId}/authentication/providers`,
    });
  } else if (authResult.googleSignInSkipped) {
    // Email/Password and Anonymous were enabled. Google Sign-In needs an OAuth client
    // that Firebase creates when you enable it in the Console for the first time.
    onProgress('auth-google-warn', {
      url: `https://console.firebase.google.com/project/${projectId}/authentication/providers`,
    });
  }

  onProgress('firestore');
  const firestoreResult = await createFirestoreDatabase(projectId, region);
  if (!firestoreResult.ok) {
    onProgress('firestore-skipped', {
      error: firestoreResult.error,
      url: `https://console.firebase.google.com/project/${projectId}/firestore`,
    });
  }

  onProgress('storage');
  const storageResult = await createFirebaseStorageBucket(projectId, region);
  if (!storageResult.ok) {
    onProgress('storage-skipped', {
      error: storageResult.error,
      url: `https://console.firebase.google.com/project/${projectId}/storage`,
    });
  }

  onProgress('android-app');
  const appResult = await createAndroidApp(projectId, bundleId, appName);
  if (!appResult.ok) return { ok: false, error: `[createAndroidApp] ${appResult.error}`, projectId };

  onProgress('ios-app');
  const iosResult = await createIosApp(projectId, bundleId, appName);
  if (!iosResult.ok) return { ok: false, error: `[createIosApp] ${iosResult.error}`, projectId };

  if (includeWeb) {
    onProgress('web-app');
    const webResult = await createWebApp(projectId, appName);
    if (!webResult.ok) return { ok: false, error: `[createWebApp] ${webResult.error}`, projectId };
  }

  let sha1Skipped = null;
  let sha1Error = null;
  const shaResult = await extractSha1();
  if (shaResult.ok) {
    onProgress('sha1');
    const shaAddResult = await addSha1ToApp(projectId, appResult.appId, shaResult.sha1);
    if (!shaAddResult.ok) {
      sha1Skipped = 'api_failed';
      sha1Error = shaAddResult.error;
    }
  } else {
    const knownError = 'Could not parse SHA1 from keytool output';
    sha1Skipped = (typeof tr === 'function' && shaResult.error === knownError)
      ? tr('new.firebase.create.sha1ParseError')
      : shaResult.error;
    sha1Error = shaResult.error;
  }

  return {
    ok: true,
    projectId,
    authEnabled: authResult.ok,
    googleSignInSkipped: authResult.ok && !!authResult.googleSignInSkipped,
    sha1Skipped,
    sha1Error,
    sha1ManualUrl: `https://console.firebase.google.com/project/${projectId}/settings/general/android:${bundleId}`,
    firestoreCreated: firestoreResult.ok,
    firestoreError: firestoreResult.ok ? null : firestoreResult.error,
    firestoreUrl: `https://console.firebase.google.com/project/${projectId}/firestore`,
    storageCreated: storageResult.ok,
    storageError: storageResult.ok ? null : storageResult.error,
    storageUrl: `https://console.firebase.google.com/project/${projectId}/storage`,
  };
}

/**
 * Setup an existing Firebase project: enable missing APIs, create Firestore and
 * Storage if they don't exist yet, and create a service account key for deploy.
 *
 * Non-fatal failures (Firestore, Storage, SHA-1, service account) are reported
 * but do not abort the flow — the user gets URLs for manual action.
 *
 * @param {string} projectId  Existing Firebase project ID
 * @param {string} bundleId   App bundle ID (for SHA-1 URL)
 * @param {object} options
 * @returns {{ ok: boolean, ... }}
 */
async function setupExistingProject(projectId, options = {}) {
  const { onProgress = () => {}, region = 'us-central1' } = options;

  const authCheck = await checkGcloudAuth();
  if (!authCheck.ok) return { ok: false, error: authCheck.error };

  onProgress('enable-apis');
  const apisResult = await enableApis(projectId);
  if (!apisResult.ok) {
    // non-fatal: user may not have gcloud set to this project — warn but continue
    onProgress('enable-apis-warn', { error: apisResult.error });
  }

  // Enable Email/Password, Anonymous and Google Sign-In auth providers automatically.
  // Non-fatal — continue even if this fails (e.g. API not yet propagated for very new projects).
  const authResult = await enableAuthProviders(projectId);
  if (!authResult.ok) {
    onProgress('auth-providers-warn', {
      error: authResult.error,
      url: `https://console.firebase.google.com/project/${projectId}/authentication/providers`,
    });
  } else if (authResult.googleSignInSkipped) {
    onProgress('auth-google-warn', {
      url: `https://console.firebase.google.com/project/${projectId}/authentication/providers`,
    });
  }

  onProgress('firestore');
  const firestoreResult = await createFirestoreDatabase(projectId);
  if (!firestoreResult.ok) {
    onProgress('firestore-skipped', {
      error: firestoreResult.error,
      url: `https://console.firebase.google.com/project/${projectId}/firestore`,
    });
  }

  onProgress('storage');
  const storageResult = await createFirebaseStorageBucket(projectId, region);
  if (!storageResult.ok) {
    onProgress('storage-skipped', {
      error: storageResult.error,
      url: `https://console.firebase.google.com/project/${projectId}/storage`,
    });
  }

  return {
    ok: true,
    projectId,
    firestoreCreated: firestoreResult.ok,
    firestoreError: firestoreResult.ok ? null : firestoreResult.error,
    firestoreUrl: `https://console.firebase.google.com/project/${projectId}/firestore`,
    storageCreated: storageResult.ok,
    storageError: storageResult.ok ? null : storageResult.error,
    storageUrl: `https://console.firebase.google.com/project/${projectId}/storage`,
  };
}

/**
 * Find the Firebase Android app ID for a given bundle ID (package name).
 * Used to register SHA-1 on existing Firebase projects where the app was created
 * by `flutterfire configure` rather than by `setupFromScratch`.
 */
async function getAndroidAppId(projectId, bundleId) {
  const token = await getAccessToken();
  const url = `https://firebase.googleapis.com/v1beta1/projects/${projectId}/androidApps?pageSize=100`;
  const res = await fetch(url, {
    headers: {
      Authorization: `Bearer ${token}`,
      'X-Goog-User-Project': projectId,
    },
  });
  if (!res.ok) {
    const text = await res.text();
    return { ok: false, error: `${res.status}: ${text}` };
  }
  const data = await res.json();
  const app = (data.apps || []).find((a) => a.packageName === bundleId);
  if (!app) return { ok: false, error: `No Android app found for package ${bundleId}` };
  return { ok: true, appId: app.appId };
}

/**
 * Register the debug keystore SHA-1 for a Firebase Android app by bundle ID.
 * Safe to call even if SHA-1 is already registered (ALREADY_EXISTS is treated as success).
 * Called for all backends when using an existing Firebase project, after flutterfire configure.
 *
 * @param {string} projectId  Firebase project ID
 * @param {string} bundleId   Android package name (e.g. com.myapp.app)
 * @returns {{ ok: boolean, existed?: boolean, sha1Error?: string }}
 */
async function registerDebugSha1(projectId, bundleId) {
  const sha1Result = await extractSha1();
  if (!sha1Result.ok) return { ok: false, sha1Error: sha1Result.error };

  const appResult = await getAndroidAppId(projectId, bundleId);
  if (!appResult.ok) return { ok: false, sha1Error: appResult.error };

  const addResult = await addSha1ToApp(projectId, appResult.appId, sha1Result.sha1);
  if (!addResult.ok) return { ok: false, sha1Error: addResult.error };

  return { ok: true, existed: !!addResult.existed };
}

module.exports = {
  setupFromScratch,
  setupExistingProject,
  checkBillingEnabled,
  enableAuthProviders,
  listBillingAccounts,
  listGcpOrganizations,
  checkGcloudAuth,
  getGcloudInstallInstructions,
  generateProjectId,
  extractSha1,
  registerDebugSha1,
  createServiceAccountAndKey,
  getServiceAccountKeyPath,
};
