/**
 * Supabase backend deployment:
 *  - Create project (supabase projects create)
 *  - Link local project
 *  - Push migrations (db push)
 *  - Enable anonymous sign-in (Management API)
 *  - Deploy Edge Functions (revenuecat-webhook if RevenueCat selected)
 *
 * Requires: supabase CLI installed, user logged in (supabase login).
 */

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

const execAsync = promisify(exec);
const execFileAsync = promisify(execFile);

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

/**
 * Check if user is logged in to Supabase.
 */
async function checkLoggedIn() {
  const result = await run('supabase projects list -o json', process.cwd());
  if (!result.ok) return { ok: false, error: result.error };
  try {
    const data = JSON.parse(result.stdout);
    return { ok: true, projects: Array.isArray(data) ? data : [] };
  } catch {
    return { ok: false, error: 'Supabase login required. Run: supabase login' };
  }
}

/**
 * List projects filtered by organization ID.
 * @param {string} orgId - Organization ID to filter by
 * @returns {{ ok: boolean, projects?: Array<{ id: string, name: string, organization_id: string }>, error?: string }}
 */
async function getProjectsByOrg(orgId) {
  const result = await checkLoggedIn();
  if (!result.ok) return result;
  const allProjects = result.projects || [];
  const filtered = allProjects.filter((p) => p.organization_id === orgId);
  return { ok: true, projects: filtered.map((p) => ({ id: p.id, name: p.name || p.id, organization_id: p.organization_id })) };
}

/**
 * List organizations (required for project create --org-id).
 * @returns {{ ok: boolean, orgs?: Array<{ id: string, name: string }>, error?: string }}
 */
async function getOrgsList() {
  const result = await run('supabase orgs list -o json', process.cwd());
  if (!result.ok) return { ok: false, error: result.error };
  try {
    const data = JSON.parse(result.stdout);
    const orgs = Array.isArray(data) ? data : [];
    return { ok: true, orgs: orgs.map((o) => ({ id: o.id, name: o.name || o.id })) };
  } catch {
    return { ok: false, error: 'Could not parse orgs list. Run: supabase login' };
  }
}

/**
 * Create a new Supabase project.
 * @param {string} projectName
 * @param {string} dbPassword
 * @param {string} [region='sa-east-1']
 * @param {string} orgId - Organization ID (required by Supabase CLI)
 * @returns {{ ok: boolean, projectRef?: string, error?: string }}
 */
async function createProject(projectName, dbPassword, region = 'sa-east-1', orgId) {
  if (!orgId) return { ok: false, error: 'Organization ID (--org-id) is required. Run: supabase orgs list' };
  const cmd = `supabase projects create "${projectName.replace(/"/g, '\\"')}" --db-password "${dbPassword.replace(/"/g, '\\"')}" --region ${region} --org-id ${orgId} -o json --yes`;
  const result = await run(cmd, process.cwd());
  if (!result.ok) return { ok: false, error: result.error };

  try {
    const data = JSON.parse(result.stdout);
    const ref = data.id || data.ref || data.project_ref || data.reference;
    if (!ref) {
      return { ok: false, error: 'Could not parse project ref from create output. Try: supabase projects list' };
    }
    return { ok: true, projectRef: String(ref) };
  } catch (e) {
    return { ok: false, error: `Parse error: ${e.message}` };
  }
}

/**
 * Get project URL and anon key.
 * API keys output format varies; we try common structures.
 */
async function getProjectKeys(projectRef) {
  const result = await run(`supabase projects api-keys --project-ref ${projectRef} -o json`, process.cwd());
  if (!result.ok) return { ok: false, error: result.error };

  try {
    const data = JSON.parse(result.stdout);
    let anonKey = data.anon_key || data.anon;
    let serviceRoleKey = data.service_role_key || data.service_role;
    if (!anonKey && Array.isArray(data)) {
      const anon = data.find((k) => k.name === 'anon' || k.title === 'anon');
      const svc = data.find((k) => k.name === 'service_role' || k.title === 'service_role');
      anonKey = anon?.key || anon?.api_key;
      serviceRoleKey = svc?.key || svc?.api_key;
    }
    if (!anonKey && data.keys) {
      const anon = data.keys.find((k) => k.name === 'anon');
      anonKey = anon?.key;
    }
    const url = `https://${projectRef}.supabase.co`;
    if (!anonKey) {
      return { ok: false, error: 'Could not parse anon key from api-keys output' };
    }
    return {
      ok: true,
      supabaseUrl: url,
      supabaseAnonKey: anonKey,
      serviceRoleKey,
    };
  } catch (e) {
    return { ok: false, error: `Parse error: ${e.message}` };
  }
}

/**
 * Get the Supabase access token stored by `supabase login`.
 * macOS: stored in Keychain under "Supabase CLI" (go-keyring-base64 encoded).
 * Fallback: SUPABASE_ACCESS_TOKEN environment variable.
 */
async function getSupabaseAccessToken() {
  if (process.env.SUPABASE_ACCESS_TOKEN) return process.env.SUPABASE_ACCESS_TOKEN;
  try {
    const { stdout } = await execAsync('security find-generic-password -s "Supabase CLI" -w');
    const raw = stdout.trim();
    if (raw.startsWith('go-keyring-base64:')) {
      return Buffer.from(raw.replace('go-keyring-base64:', ''), 'base64').toString('utf8');
    }
    return raw || null;
  } catch {
    return null;
  }
}

/**
 * Enable Google Sign-In on the Supabase project via Management API.
 *
 * Requires:
 *  - webClientId: OAuth 2.0 Web Application Client ID from Google Cloud Console
 *  - clientSecret: OAuth 2.0 Client Secret for the Web application
 *
 * skip_nonce_check is set to true to support iOS native sign-in (which does not
 * provide access to the nonce used to issue the ID token).
 */
async function enableGoogleSignIn(projectRef, webClientId, clientSecret) {
  if (!webClientId || !clientSecret) return { ok: false, error: 'webClientId and clientSecret are required' };
  const token = await getSupabaseAccessToken();
  if (!token) return { ok: false, error: 'Could not retrieve Supabase access token' };
  const payload = JSON.stringify({
    external_google_enabled: true,
    external_google_client_id: webClientId,
    external_google_secret: clientSecret,
    external_google_skip_nonce_check: true,
  });
  const result = await run(
    `curl -s -X PATCH "https://api.supabase.com/v1/projects/${projectRef}/config/auth" ` +
    `-H "Authorization: Bearer ${token}" ` +
    `-H "Content-Type: application/json" ` +
    `-d '${payload}'`,
    process.cwd()
  );
  if (!result.ok) return { ok: false, error: result.error };
  try {
    const data = JSON.parse(result.stdout);
    if (data.external_google_enabled === true) return { ok: true };
    return { ok: false, error: data.message || JSON.stringify(data) };
  } catch {
    return { ok: false, error: 'Could not parse Management API response' };
  }
}

/**
 * Configure auth settings via Supabase Management API:
 *  - Enable anonymous sign-in
 *  - Disable email confirmation (mailer_autoconfirm = true)
 *    so anonymous → email conversion works immediately on mobile.
 *    Users can re-enable email confirmation in the dashboard if needed.
 */
async function enableAnonymousSignIn(projectRef) {
  const token = await getSupabaseAccessToken();
  if (!token) return { ok: false, error: 'Could not retrieve Supabase access token' };
  const payload = JSON.stringify({
    external_anonymous_users_enabled: true,
    mailer_autoconfirm: true,
  });
  const result = await run(
    `curl -s -X PATCH "https://api.supabase.com/v1/projects/${projectRef}/config/auth" ` +
    `-H "Authorization: Bearer ${token}" ` +
    `-H "Content-Type: application/json" ` +
    `-d '${payload}'`,
    process.cwd()
  );
  if (!result.ok) return { ok: false, error: result.error };
  try {
    const data = JSON.parse(result.stdout);
    return { ok: data.external_anonymous_users_enabled === true };
  } catch {
    return { ok: false, error: 'Could not parse Management API response' };
  }
}

/**
 * Run supabase init if supabase/ folder doesn't exist.
 */
async function ensureSupabaseInit(projectDir) {
  const supabaseDir = path.join(projectDir, 'supabase');
  if (await fs.pathExists(supabaseDir)) return { ok: true, skipped: true };
  return run('supabase init', projectDir);
}

/**
 * Link project to remote.
 */
async function linkProject(projectDir, projectRef, dbPassword) {
  return run(
    `supabase link --project-ref ${projectRef} --password "${dbPassword.replace(/"/g, '\\"')}"`,
    projectDir,
    { SUPABASE_DB_PASSWORD: dbPassword }
  );
}

/**
 * Push migrations to remote database.
 */
async function dbPush(projectDir) {
  const migrationsDir = path.join(projectDir, 'supabase', 'migrations');
  if (!(await fs.pathExists(migrationsDir))) {
    return { ok: true, skipped: true };
  }
  const files = await fs.readdir(migrationsDir).catch(() => []);
  if (files.length === 0) return { ok: true, skipped: true };
  return run('supabase db push', projectDir);
}

/**
 * Set a single Supabase secret safely using execFile (no shell expansion).
 * supabase secrets set accepts KEY=VALUE as a positional arg.
 */
async function setSecret(projectDir, key, value) {
  try {
    await execFileAsync('supabase', ['secrets', 'set', `${key}=${value}`], {
      cwd: projectDir,
      maxBuffer: 10 * 1024 * 1024,
      env: process.env,
    });
    return { ok: true };
  } catch (err) {
    return { ok: false, error: err.message };
  }
}

/**
 * Set Edge Function secrets (REVENUECAT_WEBHOOK_KEY, META_ACCESS_TOKEN, META_DATASET_ID, etc.).
 * Only sets non-empty values. Run from projectDir (must be linked).
 * Uses execFile to avoid shell injection on user-supplied values.
 */
async function setSupabaseSecrets(projectDir, secrets = {}) {
  const { rcWebhookKey, metaAccessToken, metaDatasetId, firebaseProjectId, firebaseServiceAccountJson, llmApiKey, llmProvider, llmSystemPrompt } = secrets;
  const steps = [];

  if (firebaseProjectId && String(firebaseProjectId).trim()) {
    const r = await setSecret(projectDir, 'FIREBASE_PROJECT_ID', String(firebaseProjectId).trim());
    steps.push({ name: 'secret FIREBASE_PROJECT_ID', ok: r.ok, error: r.error });
  }

  if (firebaseServiceAccountJson && String(firebaseServiceAccountJson).trim()) {
    let compact;
    try {
      compact = JSON.stringify(JSON.parse(String(firebaseServiceAccountJson).trim()));
    } catch {
      compact = String(firebaseServiceAccountJson).trim();
    }
    const r = await setSecret(projectDir, 'FIREBASE_SERVICE_ACCOUNT_JSON', compact);
    steps.push({ name: 'secret FIREBASE_SERVICE_ACCOUNT_JSON', ok: r.ok, error: r.error });
  }

  if (rcWebhookKey && String(rcWebhookKey).trim()) {
    const cleanKey = String(rcWebhookKey).trim().replace(/^Bearer\s+/i, '');
    const r = await setSecret(projectDir, 'REVENUECAT_WEBHOOK_KEY', `Bearer ${cleanKey}`);
    steps.push({ name: 'secret REVENUECAT_WEBHOOK_KEY', ok: r.ok, error: r.error });
  }

  if (metaAccessToken && String(metaAccessToken).trim()) {
    const r = await setSecret(projectDir, 'META_ACCESS_TOKEN', String(metaAccessToken).trim());
    steps.push({ name: 'secret META_ACCESS_TOKEN', ok: r.ok, error: r.error });
  }

  if (metaDatasetId && String(metaDatasetId).trim()) {
    const r = await setSecret(projectDir, 'META_DATASET_ID', String(metaDatasetId).trim());
    steps.push({ name: 'secret META_DATASET_ID', ok: r.ok, error: r.error });
  }

  if (llmApiKey && String(llmApiKey).trim()) {
    const r = await setSecret(projectDir, 'LLM_API_KEY', String(llmApiKey).trim());
    steps.push({ name: 'secret LLM_API_KEY', ok: r.ok, error: r.error });
  }

  if (llmProvider && String(llmProvider).trim()) {
    const r = await setSecret(projectDir, 'LLM_PROVIDER', String(llmProvider).trim());
    steps.push({ name: 'secret LLM_PROVIDER', ok: r.ok, error: r.error });
  }

  if (llmSystemPrompt && String(llmSystemPrompt).trim()) {
    const r = await setSecret(projectDir, 'LLM_SYSTEM_PROMPT', String(llmSystemPrompt).trim());
    steps.push({ name: 'secret LLM_SYSTEM_PROMPT', ok: r.ok, error: r.error });
  }

  return steps;
}

/**
 * Deploy Edge Functions.
 * When functionNames is empty, deploys ALL functions found in supabase/functions/.
 */
async function deployFunctions(projectDir, functionNames = []) {
  const functionsDir = path.join(projectDir, 'supabase', 'functions');
  if (!(await fs.pathExists(functionsDir))) return { ok: true, skipped: true };

  let toDeploy = functionNames;
  if (toDeploy.length === 0) {
    const entries = await fs.readdir(functionsDir, { withFileTypes: true }).catch(() => []);
    toDeploy = entries.filter((e) => e.isDirectory()).map((e) => e.name);
  }
  if (toDeploy.length === 0) return { ok: true, skipped: true };

  const steps = [];
  for (const name of toDeploy) {
    const fnPath = path.join(functionsDir, name);
    if (!(await fs.pathExists(fnPath))) continue;
    const noVerifyJwt = (name === 'llm-chat' || name === 'revenuecat-webhook' || name === 'send-push-notification') ? ' --no-verify-jwt' : '';
    const result = await run(`supabase functions deploy ${name}${noVerifyJwt}`, projectDir);
    steps.push({ name: `deploy ${name}`, ok: result.ok, error: result.error });
  }
  return steps.length > 0 ? steps : { ok: true, skipped: true };
}

/**
 * Create project and get keys (run BEFORE generate - no projectDir needed).
 * @param {string} orgId - Organization ID (required)
 */
async function createProjectAndGetKeys(projectName, dbPassword, region = 'sa-east-1', orgId) {
  const loginCheck = await checkLoggedIn();
  if (!loginCheck.ok) return { ok: false, error: loginCheck.error };

  const create = await createProject(projectName, dbPassword, region, orgId);
  if (!create.ok) return create;

  const keys = await getProjectKeys(create.projectRef);
  if (!keys.ok) return { ok: false, error: keys.error };

  return {
    ok: true,
    projectRef: create.projectRef,
    supabaseUrl: keys.supabaseUrl,
    supabaseAnonKey: keys.supabaseAnonKey,
  };
}

/**
 * Setup linked project: init, link, db push, anonymous sign-in, Google Sign-In,
 * functions deploy, secrets.
 * Run AFTER generate.
 * @param {object} secrets - { rcWebhookKey, metaAccessToken, metaDatasetId, firebaseProjectId }
 * @param {object} google  - { webClientId, clientSecret } — optional, configures Google OAuth
 */
async function setupLinkedProject(projectDir, projectRef, dbPassword, secrets = {}, google = {}) {
  const steps = [];

  const init = await ensureSupabaseInit(projectDir);
  steps.push({ name: 'supabase init', ok: init.ok, skipped: init.skipped, detail: init.error });

  const link = await linkProject(projectDir, projectRef, dbPassword);
  steps.push({ name: 'supabase link', ok: link.ok, detail: link.error });
  if (!link.ok) return steps;

  const push = await dbPush(projectDir);
  steps.push({ name: 'supabase db push', ok: push.ok, skipped: push.skipped, detail: push.error });

  const anon = await enableAnonymousSignIn(projectRef);
  steps.push({ name: 'anonymous sign-in', ok: anon.ok, skipped: !anon.ok && !anon.error, detail: anon.error });

  // Configure Google Sign-In if credentials were provided
  if (google.webClientId && google.clientSecret) {
    const googleResult = await enableGoogleSignIn(projectRef, google.webClientId, google.clientSecret);
    steps.push({ name: 'google sign-in', ok: googleResult.ok, detail: googleResult.error });
  }

  // Always deploy all edge functions (send-push-notification, revenuecat-webhook, etc.)
  const fnResult = await deployFunctions(projectDir);
  if (Array.isArray(fnResult)) {
    fnResult.forEach((s) => steps.push({ name: s.name, ok: s.ok, detail: s.error }));
  } else if (!fnResult.skipped) {
    steps.push({ name: 'supabase functions deploy', ok: fnResult.ok, detail: fnResult.error });
  }

  const secretSteps = await setSupabaseSecrets(projectDir, secrets);
  secretSteps.forEach((s) => steps.push({ name: s.name, ok: s.ok, detail: s.error }));

  return steps;
}

/**
 * Full deploy sequence for new project (create + generate + setup).
 * Use createProjectAndGetKeys + setupLinkedProject for split flow.
 */
async function runDeploy(projectDir, options) {
  const {
    projectName,
    dbPassword,
    region = 'sa-east-1',
    orgId,
    deployFunctions: shouldDeployFunctions = false,
  } = options;

  const createResult = await createProjectAndGetKeys(projectName, dbPassword, region, orgId);
  if (!createResult.ok) {
    return {
      steps: [{ name: 'Criar projeto Supabase', ok: false, detail: createResult.error }],
      supabaseUrl: null,
      supabaseAnonKey: null,
    };
  }

  const setupSteps = await setupLinkedProject(
    projectDir,
    createResult.projectRef,
    dbPassword,
    shouldDeployFunctions
  );

  return {
    steps: [
      { name: 'Criar projeto Supabase', ok: true },
      { name: 'Obter API keys', ok: true },
      ...setupSteps,
    ],
    supabaseUrl: createResult.supabaseUrl,
    supabaseAnonKey: createResult.supabaseAnonKey,
    projectRef: createResult.projectRef,
  };
}

module.exports = {
  createProjectAndGetKeys,
  setupLinkedProject,
  enableAnonymousSignIn,
  enableGoogleSignIn,
  checkLoggedIn,
  getOrgsList,
  getProjectsByOrg,
  createProject,
  getProjectKeys,
  linkProject,
  dbPush,
  deployFunctions,
  setSupabaseSecrets,
};
