#!/usr/bin/env node
'use strict';
/**
 * gws-workspace setup — automates gws CLI installation, OAuth credential
 * acquisition, and GCP API enablement. Supports multiple Google accounts.
 *
 * Usage:
 *   node setup.js                      # interactive setup (default account)
 *   node setup.js --account user@example.com  # setup a specific account
 *   node setup.js --list               # list configured accounts
 *   node setup.js --switch user@example.com   # switch active account
 *   node setup.js --status             # check gws install + auth status
 *   node setup.js --enable-apis        # open browser to enable all APIs
 *
 * Can also be required: const { setupGws, getCredentials } = require('./setup');
 */
const { execFileSync } = require('child_process');
const http = require('http');
const url = require('url');
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');

// ── Binary + config resolution (corporate-friendly) ──────────────────
//
// Profiles:
//   - System profile: PATH gws + ~/.config/gws (upstream/default client).
//   - Organization helper profiles: ~/acme-ai-helpers/<platform>/gws +
//     ~/acme-ai-helpers/.config/gws (IT-approved client).
//   - Wall-E helpers profile: ~/.walle/gws-helpers/<platform>/gws +
//     ~/.walle/gws-helpers/.config/gws (packaged helper bundle).
//   - Env override: WALLE_GWS_BINARY / WALLE_GWS_CONFIG_DIR, or
//     WALLE_GWS_PROFILES='[{"label":"Work","binary":"...","configDir":"..."}]'.
//
// Why this matters: npm install -g @googleworkspace/cli ships client_id
// 482299006148-... which many corp IT departments block. Helper bundles can
// provide a pre-approved client without hard-coding organization-specific paths.
const HOME = process.env.HOME || '';
const WALL_E_HELPERS_ROOT = path.join(HOME, '.walle', 'gws-helpers');
const USER_PROFILES_FILE = process.env.WALLE_GWS_PROFILES_FILE || path.join(HOME, '.walle', 'gws-profiles.json');
const GENERIC_GWS_CLIENT_PREFIX = '482299006148-';
const GENERIC_GWS_PROJECT_ID = 'turing-agent-488400-f6';
const PERSONAL_GOOGLE_DOMAINS = new Set(['gmail.com', 'googlemail.com']);

function _detectHelpersPlatform() {
  if (process.platform === 'darwin') return process.arch === 'arm64' ? 'darwin-arm64' : 'darwin-x64';
  if (process.platform === 'linux') return process.arch === 'arm64' ? 'linux-arm64' : 'linux-x64';
  return null;
}

function _pathExists(filePath) {
  try { return fs.statSync(filePath).isFile(); } catch { return false; }
}

function _dirExists(dirPath) {
  try { return fs.statSync(dirPath).isDirectory(); } catch { return false; }
}

function _slug(value) {
  return String(value || '')
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/^-+|-+$/g, '')
    .slice(0, 40) || 'gws';
}

function _titleFromSlug(value) {
  return String(value || '')
    .replace(/[-_]+/g, ' ')
    .trim()
    .replace(/\b\w/g, ch => ch.toUpperCase()) || 'Workspace';
}

function _escapeHtml(value) {
  return String(value || '')
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;');
}

function _helperNameFromRoot(root) {
  const base = path.basename(root || '').replace(/^\.+/, '');
  const name = base
    .replace(/[-_]?ai[-_]?helpers$/i, '')
    .replace(/[-_]?helpers$/i, '')
    .replace(/^gws[-_]?/i, '');
  return name || 'workspace';
}

function _helperLabelFromRoot(root) {
  return `${_titleFromSlug(_helperNameFromRoot(root))} helper`;
}

function _normalizeConfiguredProfile(entry, idx, source) {
  if (!entry || typeof entry !== 'object') return null;
  const configDir = entry.configDir || entry.config_dir;
  if (!configDir) return null;
  return {
    id: _slug(entry.id || entry.label || `${source || 'configured'}-${idx + 1}`),
    label: entry.label || `Configured gws ${idx + 1}`,
    source: source || 'configured-profile',
    root: entry.root || null,
    binary: entry.binary || 'gws',
    configDir,
    keyringBackend: entry.keyringBackend || entry.keyring_backend || null,
    purpose: entry.purpose || null,
    preferredDomains: Array.isArray(entry.preferredDomains) ? entry.preferredDomains : [],
    userConfigured: source === 'user-profile',
  };
}

function _configuredProfilesFromJson(raw, source) {
  if (!raw) return [];
  try {
    const parsed = JSON.parse(raw);
    if (!Array.isArray(parsed)) return [];
    return parsed.map((entry, idx) => _normalizeConfiguredProfile(entry, idx, source)).filter(Boolean);
  } catch (err) {
    console.warn(`[gws-setup] Ignoring invalid ${source || 'configured'} profiles JSON:`, err.message);
    return [];
  }
}

function _configuredProfilesFromEnv() {
  return _configuredProfilesFromJson(process.env.WALLE_GWS_PROFILES, 'configured-profile');
}

function _configuredProfilesFromFile() {
  if (!_pathExists(USER_PROFILES_FILE)) return [];
  return _configuredProfilesFromJson(fs.readFileSync(USER_PROFILES_FILE, 'utf8'), 'user-profile');
}

function saveGwsProfile(entry) {
  const normalized = _normalizeConfiguredProfile({
    ...entry,
    id: entry.id || entry.label || path.basename(entry.configDir || ''),
    purpose: entry.purpose || 'Configured in Wall-E setup',
  }, 0, 'user-profile');
  if (!normalized) throw new Error('configDir is required');
  const clientSecret = path.join(normalized.configDir, 'client_secret.json');
  if (!_pathExists(clientSecret)) {
    throw new Error(`client_secret.json not found in ${normalized.configDir}`);
  }

  let existing = [];
  if (_pathExists(USER_PROFILES_FILE)) {
    try { existing = JSON.parse(fs.readFileSync(USER_PROFILES_FILE, 'utf8')); } catch { existing = []; }
  }
  if (!Array.isArray(existing)) existing = [];
  const configKey = path.resolve(normalized.configDir);
  const next = existing.filter(p => path.resolve(p.configDir || p.config_dir || '') !== configKey && _slug(p.id || p.label || '') !== normalized.id);
  next.push({
    id: normalized.id,
    label: normalized.label,
    binary: normalized.binary,
    configDir: normalized.configDir,
    keyringBackend: normalized.keyringBackend,
    purpose: normalized.purpose,
  });
  ensureDir(path.dirname(USER_PROFILES_FILE));
  fs.writeFileSync(USER_PROFILES_FILE, JSON.stringify(next, null, 2));
  return normalized;
}

function _candidateHelperRoots() {
  const roots = [];
  if (process.env.WALLE_GWS_HELPERS_ROOT) {
    roots.push({ root: process.env.WALLE_GWS_HELPERS_ROOT, source: 'env-helpers', label: 'Configured helper' });
  }
  if (process.env.AI_HELPERS_DIR) {
    roots.push({ root: process.env.AI_HELPERS_DIR, source: 'local-helper', label: _helperLabelFromRoot(process.env.AI_HELPERS_DIR) });
  }
  try {
    for (const name of fs.readdirSync(HOME)) {
      const root = path.join(HOME, name);
      if (!_dirExists(root)) continue;
      if (!/(^ai-helpers$|[-_]ai[-_]helpers$|[-_]gws[-_]helpers$)/i.test(name)) continue;
      roots.push({ root, source: 'local-helper', label: _helperLabelFromRoot(root) });
    }
  } catch {}
  const fallbackAiHelpers = path.join(HOME, 'ai-helpers');
  if (_dirExists(fallbackAiHelpers)) {
    roots.push({ root: fallbackAiHelpers, source: 'local-helper', label: _helperLabelFromRoot(fallbackAiHelpers) });
  }
  roots.push({ root: WALL_E_HELPERS_ROOT, source: 'plugin-helpers', label: 'Wall-E helper' });

  const seen = new Set();
  return roots.filter((entry) => {
    const key = path.resolve(entry.root || '');
    if (!key || seen.has(key)) return false;
    seen.add(key);
    return true;
  });
}

function _helpersBinaryForRoot(root) {
  const platform = _detectHelpersPlatform();
  if (!platform) return null;
  const p = path.join(root, platform, 'gws');
  return _pathExists(p) ? p : null;
}

function _helpersConfigDirForRoot(root) {
  const dir = path.join(root, '.config', 'gws');
  const cs = path.join(dir, 'client_secret.json');
  return _pathExists(cs) ? dir : null;
}

function _helperProfiles() {
  return _candidateHelperRoots().map((entry) => {
    const binary = _helpersBinaryForRoot(entry.root);
    const configDir = _helpersConfigDirForRoot(entry.root);
    if (!binary && !configDir) return null;
    const idPrefix = entry.source === 'plugin-helpers' ? 'walle-helper' : `helper-${_slug(_helperNameFromRoot(entry.root))}`;
    return {
      id: entry.id || idPrefix,
      label: entry.label,
      source: entry.source,
      root: entry.root,
      binary: binary || 'gws',
      configDir: configDir || path.join(entry.root, '.config', 'gws'),
      keyringBackend: entry.keyringBackend || 'file',
    };
  }).filter(Boolean);
}

function _systemProfile() {
  return {
    id: 'system',
    label: 'Generic gws',
    source: 'system',
    root: null,
    binary: 'gws',
    configDir: path.join(HOME, '.config', 'gws'),
  };
}

function _isHelperProfile(profile) {
  return !!profile && profile.source !== 'system' && profile.source !== 'env-override';
}

function getGwsProfiles() {
  const profiles = [];
  profiles.push(..._configuredProfilesFromEnv());
  profiles.push(..._configuredProfilesFromFile());
  if (process.env.WALLE_GWS_BINARY || process.env.WALLE_GWS_CONFIG_DIR) {
    profiles.push({
      id: 'env-override',
      label: 'Configured gws',
      source: 'env-override',
      root: null,
      binary: process.env.WALLE_GWS_BINARY || 'gws',
      configDir: process.env.WALLE_GWS_CONFIG_DIR || path.join(HOME, '.config', 'gws'),
      keyringBackend: process.env.GOOGLE_WORKSPACE_CLI_KEYRING_BACKEND || null,
    });
  }
  profiles.push(..._helperProfiles());
  profiles.push(_systemProfile());

  const seen = new Set();
  const usedIds = new Map();
  return profiles.filter((profile) => {
    const key = path.resolve(profile.configDir || '');
    if (!key || seen.has(key)) return false;
    seen.add(key);
    return true;
  }).map((profile) => {
    const baseId = _slug(profile.id);
    const count = usedIds.get(baseId) || 0;
    usedIds.set(baseId, count + 1);
    const id = count === 0 ? baseId : `${baseId}-${count + 1}`;
    const normalizedProfile = { ...profile, id };
    const client = getClientCredentials(normalizedProfile);
    const detected = profileToDetectedConfig(normalizedProfile, client);
    return {
      ...normalizedProfile,
      hasClientSecret: !!client,
      clientId: client ? client.clientId : null,
      projectId: client ? client.projectId : null,
      oauthClient: detected.oauthClient,
      isCorporate: detected.isCorporate,
      purpose: normalizedProfile.purpose || null,
      userConfigured: !!normalizedProfile.userConfigured,
    };
  });
}

function _activeProfile() {
  const profiles = getGwsProfiles();
  return profiles.find(p => p.source === 'env-override') ||
    profiles.find(p => _isHelperProfile(p) && p.hasClientSecret) ||
    profiles.find(p => p.source !== 'system' && p.hasClientSecret) ||
    profiles.find(p => p.source !== 'system') ||
    profiles.find(p => p.source === 'system') ||
    _systemProfile();
}

function _profileById(profileId) {
  if (!profileId) return _activeProfile();
  return getGwsProfiles().find(p => p.id === profileId) || _activeProfile();
}

function _detectHelpersBinary() {
  const profile = getGwsProfiles().find(p => p.source !== 'system' && p.binary && p.binary !== 'gws');
  return profile ? profile.binary : null;
}

function _detectHelpersConfigDir() {
  const profile = getGwsProfiles().find(p => p.source !== 'system' && p.hasClientSecret);
  return profile ? profile.configDir : null;
}

function getGwsBinary(profileId) {
  return _profileById(profileId).binary;
}

function getGwsConfigDir(profileId) {
  return _profileById(profileId).configDir;
}

// Env to pass when invoking gws binary, so it reads the right config dir.
function getGwsEnv(profileId) {
  const profile = _profileById(profileId);
  const env = { ...process.env, GOOGLE_WORKSPACE_CLI_CONFIG_DIR: profile.configDir };
  // Helpers binary stores encrypted creds with file backend; avoid keychain prompts.
  if (profile.keyringBackend) {
    env.GOOGLE_WORKSPACE_CLI_KEYRING_BACKEND = profile.keyringBackend;
  } else if (profile.source !== 'system' || process.env.WALLE_GWS_BINARY) {
    env.GOOGLE_WORKSPACE_CLI_KEYRING_BACKEND = env.GOOGLE_WORKSPACE_CLI_KEYRING_BACKEND || 'file';
  }
  return env;
}

// Resolved paths — computed once at require time. Env-var overrides take
// effect on next Wall-E restart, which is the expected pattern (similar to
// ANTHROPIC_API_KEY: set, restart, done).
const GWS_CONFIG_DIR = getGwsConfigDir();
const GWS_ACCOUNTS_DIR = path.join(GWS_CONFIG_DIR, 'accounts');
const CRED_FILE = path.join(GWS_CONFIG_DIR, 'credentials.json');
const CLIENT_SECRET_FILE = path.join(GWS_CONFIG_DIR, 'client_secret.json');

const SCOPES = [
  'https://www.googleapis.com/auth/drive',
  'https://www.googleapis.com/auth/gmail.modify',
  'https://www.googleapis.com/auth/calendar',
  'https://www.googleapis.com/auth/spreadsheets',
  'https://www.googleapis.com/auth/documents',
  'https://www.googleapis.com/auth/tasks',
  'openid',
  'https://www.googleapis.com/auth/userinfo.email',
  'https://www.googleapis.com/auth/userinfo.profile',
];

const REQUIRED_APIS = [
  { id: 'calendar-json.googleapis.com', name: 'Google Calendar API' },
  { id: 'gmail.googleapis.com', name: 'Gmail API' },
  { id: 'drive.googleapis.com', name: 'Google Drive API' },
  { id: 'sheets.googleapis.com', name: 'Google Sheets API' },
  { id: 'docs.googleapis.com', name: 'Google Docs API' },
  { id: 'tasks.googleapis.com', name: 'Google Tasks API' },
];

// ── Helpers ───────────────────────────────────────────────────────────

function ensureDir(dir) {
  if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
}

function getClientCredentials(profile) {
  const clientSecretFile = path.join((profile && profile.configDir) || GWS_CONFIG_DIR, 'client_secret.json');
  if (fs.existsSync(clientSecretFile)) {
    const data = JSON.parse(fs.readFileSync(clientSecretFile, 'utf8'));
    const inner = data.installed || data.web || data;
    if (inner.client_id && inner.client_secret) {
      return { clientId: inner.client_id, clientSecret: inner.client_secret, projectId: inner.project_id };
    }
  }
  const envId = process.env.GOOGLE_WORKSPACE_CLI_CLIENT_ID || '';
  const envSecret = process.env.GOOGLE_WORKSPACE_CLI_CLIENT_SECRET || '';
  if (envId && envSecret) return { clientId: envId, clientSecret: envSecret, projectId: null };
  return null;
}

function getProjectNumber(clientId) {
  const match = (clientId || '').match(/^(\d+)-/);
  return match ? match[1] : null;
}

function maskClientId(clientId) {
  if (!clientId) return null;
  const parts = String(clientId).split('-');
  if (parts.length < 2) return clientId.slice(0, 8) + '...';
  return `${parts[0]}-...`;
}

function classifyOAuthClient({ clientId, projectId, source, label }) {
  if (!clientId) {
    return {
      kind: 'missing',
      label: 'No OAuth client configured',
      maskedClientId: null,
      warningKind: 'missing_client',
      warningTitle: 'Google Workspace needs an OAuth client',
      warningText: 'Place client_secret.json in the active gws config directory before connecting a Google account.',
    };
  }

  if (clientId.startsWith(GENERIC_GWS_CLIENT_PREFIX) || projectId === GENERIC_GWS_PROJECT_ID) {
    return {
      kind: 'generic-gws',
      label: 'Generic gws OAuth client',
      maskedClientId: maskClientId(clientId),
      warningKind: 'generic_gws_client',
      warningTitle: 'Using the generic gws OAuth client',
      warningText: 'This is the public upstream gws OAuth client. Managed Google Workspace accounts may block it; use an organization-approved helper profile or configure WALLE_GWS_PROFILES with an approved OAuth client.',
    };
  }

  if (source !== 'system') {
    return {
      kind: 'workspace-helper',
      label: `${label || 'Workspace helper'} OAuth client`,
      maskedClientId: maskClientId(clientId),
      warningKind: 'workspace_helper_client',
      warningTitle: 'Google warning is about the OAuth app',
      warningText: 'If Google shows an app-verification or access-blocked page, that is about the selected OAuth app/profile, not an account problem. Use an organization-approved helper profile for managed accounts.',
    };
  }

  return {
    kind: 'custom',
    label: 'Custom OAuth client',
    maskedClientId: maskClientId(clientId),
    warningKind: null,
    warningTitle: null,
    warningText: null,
  };
}

function profileToDetectedConfig(profile, client) {
  const clientId = client ? client.clientId : null;
  const projectId = client ? client.projectId : null;
  const isCorporate =
    _isHelperProfile(profile) || (!!projectId && /(^corp-|corporate|managed|workspace|gws|-skills$|-cli-|walle)/.test(projectId));
  return {
    binary: profile.binary,
    configDir: profile.configDir,
    source: profile.source,
    profileId: profile.id,
    profileLabel: profile.label,
    helpersAvailable: profile.source !== 'system',
    clientId,
    projectId,
    isCorporate,
    hasClientSecret: !!clientId,
    oauthClient: classifyOAuthClient({ clientId, projectId, source: profile.source, label: profile.label }),
  };
}

function describeGoogleAccount(email) {
  const raw = typeof email === 'string' ? email.trim() : '';
  const domain = raw.includes('@') ? raw.split('@').pop().toLowerCase() : '';
  if (!domain) {
    return {
      accountKind: 'google',
      accountLabel: 'Google',
      accountDescription: 'Google account',
      domain: null,
    };
  }
  if (PERSONAL_GOOGLE_DOMAINS.has(domain)) {
    return {
      accountKind: 'personal',
      accountLabel: 'Personal',
      accountDescription: 'Personal Google account',
      domain,
    };
  }
  return {
    accountKind: 'work',
    accountLabel: 'Work',
    accountDescription: `Workspace: ${domain}`,
    domain,
  };
}

function _isPersonalGoogleAccount(email) {
  const domain = String(email || '').split('@').pop().toLowerCase();
  return PERSONAL_GOOGLE_DOMAINS.has(domain);
}

function _profileCredentialsFile(profile) {
  return path.join(profile.configDir, 'credentials.json');
}

function _profileAccountsDir(profile) {
  return path.join(profile.configDir, 'accounts');
}

function _safeAccountEmail(email) {
  if (!email || typeof email !== 'string' || email.includes('/') || email.includes('\\') || email === '.' || email === '..') {
    throw new Error(`Invalid account name: ${email || '(empty)'}`);
  }
  return email;
}

function httpsRequest(urlStr, opts = {}) {
  return new Promise((resolve, reject) => {
    const https = require('https');
    const parsed = new URL(urlStr);
    const reqOpts = {
      hostname: parsed.hostname,
      port: parsed.port || 443,
      path: parsed.pathname + parsed.search,
      method: opts.method || 'GET',
      headers: opts.headers || {},
    };
    const req = https.request(reqOpts, (res) => {
      let body = '';
      res.on('data', (chunk) => body += chunk);
      res.on('end', () => resolve({ status: res.statusCode, body }));
    });
    req.on('error', reject);
    if (opts.body) req.write(opts.body);
    req.end();
  });
}

// ── OAuth Flow ────────────────────────────────────────────────────────

async function runOAuthFlow(clientId, clientSecret, opts = {}) {
  return new Promise((resolve, reject) => {
    let authCode = null;
    const expectedState = crypto.randomBytes(16).toString('hex');

    const server = http.createServer((req, res) => {
      const parsed = url.parse(req.url, true);
      if (parsed.query.code) {
        if (parsed.query.state !== expectedState) {
          res.writeHead(400, { 'Content-Type': 'text/html' });
          res.end('<html><body><h2>OAuth state mismatch</h2><p>Please restart sign-in from CTM.</p></body></html>');
          server.close();
          reject(new Error('OAuth state mismatch'));
          return;
        }
        authCode = parsed.query.code;
        res.writeHead(200, { 'Content-Type': 'text/html' });
        res.end('<html><body><h2>Authentication successful!</h2><p>You can close this tab.</p></body></html>');
        server.close();
      } else if (parsed.query.error) {
        res.writeHead(400, { 'Content-Type': 'text/html' });
        const desc = parsed.query.error_description || '';
        res.end(`<html><body><h2>Error: ${_escapeHtml(parsed.query.error)}</h2><p>${_escapeHtml(desc)}</p></body></html>`);
        server.close();
        reject(new Error(`OAuth error: ${parsed.query.error} — ${desc}`));
      }
    });

    // Fixed port range for Desktop app redirect URI compatibility
    const PORT = 8085 + Math.floor(Math.random() * 10);

    server.listen(PORT, 'localhost', () => {
      const redirectUri = `http://localhost:${PORT}`;
      const params = new URLSearchParams({
        client_id: clientId,
        redirect_uri: redirectUri,
        response_type: 'code',
        scope: SCOPES.join(' '),
        access_type: 'offline',
        prompt: 'consent select_account',
        include_granted_scopes: 'true',
        state: expectedState,
      });
      if (opts.loginHint) params.set('login_hint', opts.loginHint);
      const authUrl = `https://accounts.google.com/o/oauth2/auth?${params.toString()}`;

      console.log(`\nOpening browser for Google OAuth consent...`);
      console.log(`If it doesn't open, visit:\n${authUrl}\n`);

      try {
        if (process.platform === 'darwin') {
          execFileSync('open', [authUrl], { timeout: 5000 });
        } else if (process.platform === 'linux') {
          execFileSync('xdg-open', [authUrl], { stdio: 'ignore', timeout: 5000 });
        }
      } catch { /* user can open manually */ }

      const timeout = setTimeout(() => {
        server.close();
        reject(new Error('OAuth flow timed out after 2 minutes'));
      }, 120000);

      server.on('close', async () => {
        clearTimeout(timeout);
        if (!authCode) return;

        try {
          const tokenData = new URLSearchParams({
            code: authCode,
            client_id: clientId,
            client_secret: clientSecret,
            redirect_uri: `http://localhost:${PORT}`,
            grant_type: 'authorization_code',
          }).toString();

          const resp = await httpsRequest('https://oauth2.googleapis.com/token', {
            method: 'POST',
            headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
            body: tokenData,
          });

          if (resp.status !== 200) {
            reject(new Error(`Token exchange failed (${resp.status}): ${resp.body}`));
            return;
          }
          resolve(JSON.parse(resp.body));
        } catch (err) {
          reject(err);
        }
      });
    });
  });
}

async function fetchAuthenticatedEmail(accessToken) {
  if (!accessToken) throw new Error('OAuth response did not include an access token');
  const resp = await httpsRequest('https://www.googleapis.com/oauth2/v2/userinfo', {
    headers: { Authorization: `Bearer ${accessToken}` },
  });
  if (resp.status < 200 || resp.status >= 300) {
    throw new Error(`Google userinfo failed (${resp.status})`);
  }
  let data;
  try {
    data = JSON.parse(resp.body || '{}');
  } catch {
    throw new Error('Google userinfo returned invalid JSON');
  }
  if (!data.email || typeof data.email !== 'string') {
    throw new Error('Google userinfo did not return an email address');
  }
  return data.email.trim();
}

function _sameGoogleEmail(a, b) {
  return String(a || '').trim().toLowerCase() === String(b || '').trim().toLowerCase();
}

async function refreshAccessToken(clientId, clientSecret, refreshToken) {
  const tokenData = new URLSearchParams({
    client_id: clientId,
    client_secret: clientSecret,
    refresh_token: refreshToken,
    grant_type: 'refresh_token',
  }).toString();

  const resp = await httpsRequest('https://oauth2.googleapis.com/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: tokenData,
  });

  if (resp.status !== 200) throw new Error(`Token refresh failed (${resp.status}): ${resp.body}`);
  return JSON.parse(resp.body);
}

// ── Account Management ────────────────────────────────────────────────

function _accountFile(email, profile) {
  return path.join(_profileAccountsDir(profile || _activeProfile()), `${_safeAccountEmail(email)}.json`);
}

function _readJsonFile(filePath) {
  try {
    return JSON.parse(fs.readFileSync(filePath, 'utf8'));
  } catch {
    return null;
  }
}

function _accountRecord(profile, email, data) {
  const accountInfo = describeGoogleAccount(email);
  return {
    email,
    ...accountInfo,
    scope: data.scope || '',
    active: isActiveAccount(email, profile),
    reauthRequired: !!data._reauth_required,
    lastRefreshError: data._last_refresh_error || null,
    hasAccessToken: !!data.access_token,
    hasRefreshToken: !!data.refresh_token,
    clientId: data.client_id || null,
    profileId: profile.id,
    profileLabel: profile.label,
    profileSource: profile.source,
    profileConfigDir: profile.configDir,
    oauthClient: profile.oauthClient || null,
  };
}

function listAccounts() {
  const records = [];
  const seen = new Set();
  for (const profile of getGwsProfiles()) {
    const accountsDir = _profileAccountsDir(profile);
    if (fs.existsSync(accountsDir)) {
      const files = fs.readdirSync(accountsDir).filter(f => f.endsWith('.json'));
      for (const f of files) {
        const data = _readJsonFile(path.join(accountsDir, f));
        if (!data) continue;
        const email = f.replace(/\.json$/, '');
        const key = `${profile.id}:${email}`;
        seen.add(key);
        records.push(_accountRecord(profile, email, data));
      }
    }

    const active = _readJsonFile(_profileCredentialsFile(profile));
    if (active && active._account) {
      const email = active._account;
      const key = `${profile.id}:${email}`;
      if (!seen.has(key)) {
        seen.add(key);
        records.push(_accountRecord(profile, email, active));
      }
    }
  }
  return records.sort((a, b) => {
    if (a.active !== b.active) return a.active ? -1 : 1;
    if (a.profileSource !== b.profileSource) {
      if (a.profileSource !== 'system' && b.profileSource === 'system') return -1;
      if (b.profileSource !== 'system' && a.profileSource === 'system') return 1;
    }
    return a.email.localeCompare(b.email);
  });
}

function isActiveAccount(email, profile) {
  const active = _readJsonFile(_profileCredentialsFile(profile || _activeProfile()));
  return !!(active && active._account === email);
}

function _findAccountProfile(email, preferredProfileId) {
  _safeAccountEmail(email);
  const profiles = getGwsProfiles();
  const ordered = preferredProfileId
    ? profiles.filter(p => p.id === preferredProfileId).concat(profiles.filter(p => p.id !== preferredProfileId))
    : profiles;
  for (const profile of ordered) {
    const accountFile = _accountFile(email, profile);
    if (fs.existsSync(accountFile)) {
      return { profile, accountFile, creds: _readJsonFile(accountFile) };
    }
  }
  for (const profile of ordered) {
    const credFile = _profileCredentialsFile(profile);
    const active = _readJsonFile(credFile);
    if (active && active._account === email) {
      return { profile, accountFile: _accountFile(email, profile), creds: active };
    }
  }
  return null;
}

function _selectProfileForAccount(email, profileId) {
  const profiles = getGwsProfiles();
  if (profileId) {
    const explicit = profiles.find(p => p.id === profileId);
    if (!explicit) throw new Error(`Unknown gws profile: ${profileId}`);
    return explicit;
  }

  const existing = email ? _findAccountProfile(email) : null;
  if (existing) return existing.profile;

  const hasClient = p => p && p.hasClientSecret;
  if (email && _isPersonalGoogleAccount(email)) {
    return profiles.find(p => p.source === 'system' && hasClient(p)) ||
      profiles.find(p => p.source === 'env-override' && hasClient(p)) ||
      _activeProfile();
  }

  if (email && describeGoogleAccount(email).accountKind === 'work') {
    return profiles.find(p => _isHelperProfile(p) && hasClient(p)) ||
      profiles.find(p => p.source !== 'system' && hasClient(p)) ||
      profiles.find(p => p.source === 'system' && hasClient(p)) ||
      _activeProfile();
  }

  return _activeProfile();
}

function _profileWithActiveCredentials() {
  const preferred = _activeProfile();
  const profiles = [preferred, ...getGwsProfiles().filter(p => p.id !== preferred.id)];
  for (const profile of profiles) {
    const active = _readJsonFile(_profileCredentialsFile(profile));
    if (active && active._account) return profile;
  }
  return null;
}

function saveAccountCredentials(email, tokens, clientId, clientSecret, profile) {
  const targetProfile = profile || _selectProfileForAccount(email);
  ensureDir(_profileAccountsDir(targetProfile));
  const creds = {
    type: 'authorized_user',
    client_id: clientId,
    client_secret: clientSecret,
    access_token: tokens.access_token,
    refresh_token: tokens.refresh_token,
    token_type: tokens.token_type || 'Bearer',
    scope: tokens.scope || SCOPES.join(' '),
    expires_at: Math.floor(Date.now() / 1000) + (tokens.expires_in || 3600),
    _account: email,
  };
  fs.writeFileSync(_accountFile(email, targetProfile), JSON.stringify(creds, null, 2));
  return creds;
}

function switchAccount(email, profileId) {
  const found = _findAccountProfile(email, profileId);
  if (!found || !found.creds) {
    throw new Error(`Account ${email} not found. Run: node setup.js --account ${email}`);
  }
  ensureDir(found.profile.configDir);
  fs.writeFileSync(_profileCredentialsFile(found.profile), JSON.stringify(found.creds, null, 2));
  return found.creds;
}

function removeAccount(email, profileId) {
  _safeAccountEmail(email);
  const profiles = profileId ? [_selectProfileForAccount(email, profileId)] : getGwsProfiles();
  let removed = false;
  for (const profile of profiles) {
    const accountFile = _accountFile(email, profile);
    if (fs.existsSync(accountFile)) {
      fs.unlinkSync(accountFile);
      removed = true;
    }
    const credFile = _profileCredentialsFile(profile);
    const active = _readJsonFile(credFile);
    if (active && active._account === email) {
      fs.unlinkSync(credFile);
      removed = true;
    }
  }
  return removed;
}

function _credentialSavePaths(email, profile) {
  const targetProfile = profile || _selectProfileForAccount(email);
  const paths = [];
  if (email) {
    paths.push(_accountFile(email, targetProfile));
    if (isActiveAccount(email, targetProfile)) paths.push(_profileCredentialsFile(targetProfile));
  } else {
    paths.push(_profileCredentialsFile(targetProfile));
    const active = _readJsonFile(_profileCredentialsFile(targetProfile));
    if (active && active._account) paths.push(_accountFile(active._account, targetProfile));
  }
  return Array.from(new Set(paths));
}

function _writeCredentials(email, creds, profile) {
  const targetProfile = profile || _selectProfileForAccount(email || creds?._account);
  ensureDir(targetProfile.configDir);
  ensureDir(_profileAccountsDir(targetProfile));
  for (const savePath of _credentialSavePaths(email || creds?._account || null, targetProfile)) {
    fs.writeFileSync(savePath, JSON.stringify(creds, null, 2));
  }
}

function _markCredentialsReauthRequired(email, creds, err, profile) {
  const next = {
    ...creds,
    _reauth_required: true,
    _last_refresh_error: err && err.message ? err.message : String(err || 'token refresh failed'),
    _last_refresh_error_at: new Date().toISOString(),
    expires_at: 0,
  };
  delete next.access_token;
  _writeCredentials(email, next, profile);
  return next;
}

function _isPermanentRefreshFailure(err) {
  const msg = err && err.message ? err.message : String(err || '');
  return /invalid_grant|missing refresh_token|revoked|expired or revoked/i.test(msg);
}

async function getCredentials(email, opts = {}) {
  let creds;
  let profile;
  if (email) {
    const found = _findAccountProfile(email, opts.profileId);
    if (!found) return null;
    profile = found.profile;
    creds = found.creds;
  } else {
    profile = opts.profileId ? _selectProfileForAccount(null, opts.profileId) : (_profileWithActiveCredentials() || _selectProfileForAccount(null));
    creds = _readJsonFile(_profileCredentialsFile(profile));
    if (!creds) return null;
  }

  if (creds._reauth_required && !creds.access_token) return null;

  // Refresh if expired (60s buffer)
  if (creds.expires_at && creds.expires_at < Math.floor(Date.now() / 1000) + 60) {
    try {
      if (!creds.refresh_token) throw new Error('missing refresh_token');
      const refreshFn = opts.refreshAccessToken || refreshAccessToken;
      const refreshed = await refreshFn(creds.client_id, creds.client_secret, creds.refresh_token);
      creds.access_token = refreshed.access_token;
      creds.expires_at = Math.floor(Date.now() / 1000) + (refreshed.expires_in || 3600);
      delete creds._reauth_required;
      delete creds._last_refresh_error;
      delete creds._last_refresh_error_at;
      _writeCredentials(email || creds._account, creds, profile);
    } catch (err) {
      console.error(`[gws-setup] Token refresh failed for ${email || 'active'}: ${err.message}`);
      if (_isPermanentRefreshFailure(err)) _markCredentialsReauthRequired(email || creds._account, creds, err, profile);
      return null;
    }
  }
  return creds;
}

// ── GWS Install Check ─────────────────────────────────────────────────

function isGwsInstalled(profileId) {
  try {
    execFileSync(getGwsBinary(profileId), ['--version'], { stdio: 'pipe', timeout: 10000, env: getGwsEnv(profileId) });
    return true;
  } catch { return false; }
}

function installGws(profileId) {
  const profile = _profileById(profileId);
  if (profile.source !== 'system' && profile.binary && profile.binary !== 'gws') {
    console.log('[gws-setup] Using helper gws binary (' + profile.binary + ')');
    return;
  }
  // Skip if a plugin-shipped binary is already available — installing the
  // generic npm package would shadow the IT-approved one on PATH.
  if (!profileId && _detectHelpersBinary()) {
    console.log('[gws-setup] Using plugin-shipped gws binary (' + _detectHelpersBinary() + ')');
    return;
  }
  console.log('[gws-setup] Installing gws CLI via npm...');
  execFileSync('npm', ['install', '-g', '@googleworkspace/cli'], { stdio: 'inherit', timeout: 120000 });
}

// Diagnostic info for the UI — shows which binary/config is active and
// whether the active client is a known corporate-approved one.
function getDetectedConfig() {
  const active = _activeProfile();
  return {
    ...profileToDetectedConfig(active, getClientCredentials(active)),
    helpersAvailable: !!_detectHelpersBinary() && !!_detectHelpersConfigDir(),
    profiles: getGwsProfiles().map((profile) => ({
      id: profile.id,
      label: profile.label,
      source: profile.source,
      binary: profile.binary,
      configDir: profile.configDir,
      hasClientSecret: profile.hasClientSecret,
      clientId: profile.clientId,
      projectId: profile.projectId,
      isCorporate: profile.isCorporate,
      oauthClient: profile.oauthClient,
      purpose: profile.purpose || null,
      userConfigured: !!profile.userConfigured,
      active: profile.id === active.id,
    })),
  };
}

// ── API Enablement ────────────────────────────────────────────────────

function getApiEnableUrls(projectNumber) {
  return REQUIRED_APIS.map(api => ({
    ...api,
    url: `https://console.developers.google.com/apis/api/${api.id}/overview?project=${projectNumber}`,
  }));
}

function openApiEnablePages(projectNumber) {
  const apis = getApiEnableUrls(projectNumber);
  console.log('\nEnable these APIs in your GCP project:\n');
  for (const api of apis) {
    console.log(`  ${api.name}: ${api.url}`);
  }
  if (process.platform === 'darwin') {
    for (const api of apis) {
      try { execFileSync('open', [api.url], { timeout: 5000 }); } catch {}
    }
  }
}

async function testApis(accessToken) {
  const tests = [
    { name: 'Calendar', url: 'https://www.googleapis.com/calendar/v3/users/me/calendarList?maxResults=1' },
    { name: 'Gmail', url: 'https://www.googleapis.com/gmail/v1/users/me/messages?maxResults=1' },
    { name: 'Drive', url: 'https://www.googleapis.com/drive/v3/files?pageSize=1' },
    { name: 'Sheets', url: 'https://sheets.googleapis.com/v4/spreadsheets/nonexistent' },
    { name: 'Docs', url: 'https://docs.googleapis.com/v1/documents/nonexistent' },
    { name: 'Tasks', url: 'https://www.googleapis.com/tasks/v1/users/@me/lists?maxResults=1' },
  ];

  const results = [];
  for (const test of tests) {
    try {
      const resp = await httpsRequest(test.url, {
        headers: { Authorization: `Bearer ${accessToken}` },
      });
      const notEnabled = resp.body.includes('accessNotConfigured') || resp.body.includes('SERVICE_DISABLED');
      results.push({ name: test.name, enabled: !notEnabled, status: resp.status });
    } catch (err) {
      results.push({ name: test.name, enabled: false, error: err.message });
    }
  }
  return results;
}

// ── Main Setup Flow ───────────────────────────────────────────────────

async function setupGws(opts = {}) {
  const account = opts.account || null;
  const profile = _selectProfileForAccount(account, opts.profileId);

  if (!isGwsInstalled(profile.id)) {
    console.log('[gws-setup] gws CLI not found. Installing...');
    installGws(profile.id);
  } else {
    const version = execFileSync(getGwsBinary(profile.id), ['--version'], {
      encoding: 'utf8',
      env: getGwsEnv(profile.id),
    }).trim();
    console.log(`[gws-setup] gws CLI found (${profile.label}): ${version}`);
  }

  const client = getClientCredentials(profile);
  if (!client) {
    console.error('[gws-setup] No OAuth client credentials found.');
    console.error(`[gws-setup] Place client_secret.json in ${profile.configDir}/ or set env vars.`);
    return { success: false, error: 'no_client_credentials' };
  }
  console.log(`[gws-setup] OAuth client (${profile.label}): ${client.clientId.slice(0, 12)}...`);

  console.log(`[gws-setup] Starting OAuth flow${account ? ` for ${account}` : ''}...`);
  let tokens;
  try {
    const oauthFlow = opts.runOAuthFlow || runOAuthFlow;
    tokens = await oauthFlow(client.clientId, client.clientSecret, {
      loginHint: account || undefined,
      profileId: profile.id,
    });
  } catch (err) {
    return { success: false, error: `oauth_failed: ${err.message}` };
  }
  console.log(`[gws-setup] Got tokens. Scopes: ${tokens.scope}`);
  if (!tokens.access_token) {
    return { success: false, error: 'oauth_failed: Google did not return an access token' };
  }
  if (!tokens.refresh_token) {
    return {
      success: false,
      error: 'offline_token_missing: Google did not return a refresh token. Reconnect and approve offline access so Wall-E can sync while you are away.',
    };
  }

  let authenticatedEmail = null;
  try {
    const fetchEmail = opts.fetchAuthenticatedEmail || fetchAuthenticatedEmail;
    authenticatedEmail = await fetchEmail(tokens.access_token);
    console.log(`[gws-setup] Authenticated as: ${authenticatedEmail}`);
  } catch (err) {
    if (account) {
      return { success: false, error: `account_verification_failed: ${err.message}` };
    }
    console.warn(`[gws-setup] Could not verify Google account email: ${err.message}`);
  }

  if (account && authenticatedEmail && !_sameGoogleEmail(account, authenticatedEmail)) {
    return {
      success: false,
      error: `account_mismatch: signed in as ${authenticatedEmail}, but this reconnect is for ${account}. Choose ${account} in Google sign-in or add ${authenticatedEmail} as a separate account.`,
      email: authenticatedEmail,
      expectedEmail: account,
      profileId: profile.id,
    };
  }

  const email = account || authenticatedEmail || 'default';
  const finalProfile = _selectProfileForAccount(email, profile.id);
  const creds = saveAccountCredentials(email, tokens, client.clientId, client.clientSecret, finalProfile);
  ensureDir(finalProfile.configDir);
  fs.writeFileSync(_profileCredentialsFile(finalProfile), JSON.stringify(creds, null, 2));
  console.log(`[gws-setup] Credentials saved for ${email} (${finalProfile.label})`);

  console.log('[gws-setup] Testing API access...');
  const apiTestFn = opts.testApis || testApis;
  const apiResults = await apiTestFn(tokens.access_token);
  const allEnabled = apiResults.every(r => r.enabled);

  for (const r of apiResults) {
    console.log(`  ${r.enabled ? 'OK' : 'DISABLED'} ${r.name}`);
  }

  if (!allEnabled) {
    const projectNumber = getProjectNumber(client.clientId);
    if (projectNumber) {
      console.log('\n[gws-setup] Some APIs are not enabled. Opening GCP Console...');
      openApiEnablePages(projectNumber);
    }
  }

  return {
    success: true,
    email,
    profileId: finalProfile.id,
    profileLabel: finalProfile.label,
    profileSource: finalProfile.source,
    apisEnabled: apiResults,
    allApisEnabled: allEnabled,
  };
}

// ── CLI ───────────────────────────────────────────────────────────────

async function main() {
  const args = process.argv.slice(2);

  if (args.includes('--status')) {
    const installed = isGwsInstalled();
    const client = getClientCredentials();
    const accounts = listAccounts();
    console.log(JSON.stringify({
      installed,
      hasClient: !!client,
      accounts,
      detectedConfig: getDetectedConfig(),
      profiles: getGwsProfiles(),
    }, null, 2));
    return;
  }

  if (args.includes('--list')) {
    const accounts = listAccounts();
    if (accounts.length === 0) { console.log('No accounts configured. Run: node setup.js'); return; }
    for (const a of accounts) console.log(`${a.active ? '* ' : '  '}${a.email}`);
    return;
  }

  if (args.includes('--switch')) {
    const idx = args.indexOf('--switch');
    const email = args[idx + 1];
    if (!email) { console.error('Usage: --switch <email>'); process.exit(1); }
    switchAccount(email);
    console.log(`Switched active account to ${email}`);
    return;
  }

  if (args.includes('--enable-apis')) {
    const profile = _activeProfile();
    const client = getClientCredentials(profile);
    if (!client) { console.error('No client credentials found'); process.exit(1); }
    const projectNumber = getProjectNumber(client.clientId);
    if (!projectNumber) { console.error('Cannot determine project number'); process.exit(1); }
    openApiEnablePages(projectNumber);
    return;
  }

  const accountIdx = args.indexOf('--account');
  const account = accountIdx >= 0 ? args[accountIdx + 1] : null;
  const result = await setupGws({ account });
  if (!result.success) { console.error(`[gws-setup] Setup failed: ${result.error}`); process.exit(1); }
  console.log('\n[gws-setup] Setup complete!');
}

module.exports = {
  setupGws,
  getCredentials,
  getClientCredentials,
  switchAccount,
  listAccounts,
  refreshAccessToken,
  getGwsProfiles,
  isGwsInstalled,
  removeAccount,
  saveGwsProfile,
  testApis,
  getApiEnableUrls,
  GWS_CONFIG_DIR,
  GWS_ACCOUNTS_DIR,
  // Corporate-friendly binary/config resolution
  getGwsBinary,
  getGwsConfigDir,
  getGwsEnv,
  getDetectedConfig,
  describeGoogleAccount,
};

if (require.main === module) {
  main().catch(err => { console.error(`[gws-setup] Fatal: ${err.message}`); process.exit(1); });
}
