const path = require('node:path');
const { spawnSync } = require('node:child_process');
const fs = require('fs-extra');
const kleur = require('kleur');
const ui = require('../utils/ui');
const { createTranslator, detectDefaultLanguage } = require('../utils/i18n');
const { printCompactHeader } = require('../utils/brand');
const { spawnFlutterWithSpinner } = require('../utils/flutter-run');

function listFlutterDevices(projectDir) {
  const res = spawnSync('flutter', ['devices', '--machine'], {
    cwd: projectDir,
    encoding: 'utf8',
  });
  if (res.status !== 0) return [];
  try {
    const parsed = JSON.parse(res.stdout);
    return Array.isArray(parsed) ? parsed : [];
  } catch {
    return [];
  }
}

function classifyDevice(device) {
  const platform = (device.targetPlatform || '').toLowerCase();
  if (platform === 'ios') return device.emulator ? 'ios-simulator' : 'ios-device';
  if (platform.startsWith('android')) {
    return device.emulator ? 'android-emulator' : 'android-device';
  }
  if (platform.startsWith('web')) return 'web';
  if (platform.startsWith('darwin')) return 'macos';
  if (platform.startsWith('linux')) return 'linux';
  if (platform.startsWith('windows')) return 'windows';
  return 'unknown';
}

async function pickDevice(devices, t) {
  if (devices.length === 0) return null;
  if (devices.length === 1) return devices[0];
  const choice = await ui.select({
    message: t('run.prompt.pickDevice'),
    options: devices.map((d) => ({
      value: d.id,
      label: `${d.name} ${kleur.dim(`(${classifyDevice(d)})`)}`,
    })),
  });
  return devices.find((d) => d.id === choice) || null;
}

/**
 * Read dart-define args from .vscode/launch.json.
 * Returns the args array from the matching config (dev or prod).
 */
async function readLaunchArgs(projectDir, useProd) {
  const launchPath = path.join(projectDir, '.vscode', 'launch.json');
  if (!(await fs.pathExists(launchPath))) return [];
  try {
    const launch = await fs.readJson(launchPath);
    const configs = launch.configurations || [];
    const keyword = useProd ? 'prod' : 'dev';
    const found = configs.find((c) => c.name?.toLowerCase().includes(keyword));
    if (!found && useProd && configs[0]) {
      console.warn(kleur.yellow(`  ⚠  No "${keyword}" config found in launch.json — using "${configs[0].name}" instead`));
    }
    const config = found || configs[0];
    return Array.isArray(config?.args) ? config.args : [];
  } catch {
    return [];
  }
}

/**
 * Parse the project `.env` into a plain object.
 * Supports KEY=VALUE lines; ignores comments and blank lines.
 * Quotes around values are stripped. Missing file returns {}.
 */
async function loadEnvFile(projectDir) {
  const envPath = path.join(projectDir, '.env');
  if (!(await fs.pathExists(envPath))) return {};
  const content = await fs.readFile(envPath, 'utf8');
  const env = {};
  for (const rawLine of content.split('\n')) {
    const line = rawLine.trim();
    if (!line || line.startsWith('#')) continue;
    const eq = line.indexOf('=');
    if (eq === -1) continue;
    const key = line.slice(0, eq).trim();
    let value = line.slice(eq + 1).trim();
    if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
      value = value.slice(1, -1);
    }
    if (key) env[key] = value;
  }
  return env;
}

/**
 * Decide whether the chosen device is a physical phone or a simulator/emulator.
 * Returns one of: 'ios-simulator' | 'ios-device' | 'android-emulator' |
 * 'android-device' | 'web' | 'desktop' | 'unknown'.
 *
 * When the user passes a generic `--ios`/`--android` shortcut we list devices
 * and use the only matching one. With ambiguous matches we return
 * 'ios-unknown' / 'android-unknown' so the caller can pick a safe default
 * (test key) and warn the user.
 */
function classifyTargetDevice(projectDir, options, pickedDevice) {
  if (options.web) return 'web';
  if (pickedDevice) return classifyDevice(pickedDevice);

  const devices = listFlutterDevices(projectDir);

  if (options.device) {
    const match = devices.find((d) => d.id === options.device || d.name === options.device);
    return match ? classifyDevice(match) : 'unknown';
  }

  if (options.ios) {
    const ios = devices.filter((d) => (d.targetPlatform || '').toLowerCase() === 'ios');
    if (ios.length === 1) return classifyDevice(ios[0]);
    return 'ios-unknown';
  }
  if (options.android) {
    const android = devices.filter((d) => (d.targetPlatform || '').toLowerCase().startsWith('android'));
    if (android.length === 1) return classifyDevice(android[0]);
    return 'android-unknown';
  }

  // No flag, no picked device: flutter run picks the only device available.
  // Use it to choose the right RC key when there's exactly one mobile device.
  const mobile = devices.filter((d) => {
    const p = (d.targetPlatform || '').toLowerCase();
    return p === 'ios' || p.startsWith('android');
  });
  if (mobile.length === 1) return classifyDevice(mobile[0]);

  return 'unknown';
}

/**
 * Resolve which RC keys to inject given the .env contents, the classified
 * device, and the requested mode ('auto' | 'test' | 'prod').
 *
 * Returns { android, ios, mode, warnings } where `mode` is 'test' or 'prod'
 * (informational) and `warnings` is an array of human-readable strings.
 *
 * Behavior:
 *   - 'test' mode → both keys = RC_TEST_KEY (or empty if missing).
 *   - 'prod' mode → ios = RC_IOS_PROD_KEY, android = RC_ANDROID_PROD_KEY.
 *     If either is missing we warn and fall back to RC_TEST_KEY for that one.
 *   - 'auto' mode → infer from device class:
 *       simulator/emulator/unknown → test
 *       ios-device → prod for iOS define (fallback test), test for Android
 *       android-device → prod for Android define (fallback test), test for iOS
 *
 * Legacy fallback: if .env has no RC_TEST_KEY / RC_*_PROD_KEY but has the
 * old RC_ANDROID_API_KEY / RC_IOS_API_KEY, we honor those as-is so projects
 * generated before the test/prod split keep working without manual migration.
 */
function resolveRcKeys(env, deviceClass, mode, t) {
  const warnings = [];
  const legacyAndroid = env.RC_ANDROID_API_KEY;
  const legacyIos = env.RC_IOS_API_KEY;
  const test = env.RC_TEST_KEY;
  const iosProd = env.RC_IOS_PROD_KEY;
  const androidProd = env.RC_ANDROID_PROD_KEY;

  const hasNewKeys = !!(test || iosProd || androidProd);
  if (!hasNewKeys && (legacyAndroid || legacyIos)) {
    return {
      android: legacyAndroid || '',
      ios: legacyIos || '',
      mode: 'legacy',
      warnings,
    };
  }

  const isSimulator = deviceClass === 'ios-simulator' || deviceClass === 'android-emulator';
  const isIosDevice = deviceClass === 'ios-device';
  const isAndroidDevice = deviceClass === 'android-device';

  // Resolve effective mode.
  let effectiveMode = mode;
  if (mode === 'auto') {
    effectiveMode = (isIosDevice || isAndroidDevice) ? 'prod' : 'test';
  }

  // Build android+ios.
  let android;
  let ios;

  if (effectiveMode === 'test') {
    android = test || '';
    ios = test || '';
  } else {
    // prod mode — pick per platform, fall back to test if prod missing.
    if (isIosDevice) {
      ios = iosProd || test || '';
      android = test || '';
      if (!iosProd) {
        warnings.push(t('run.rc.fallbackToTest', { platform: 'iOS', var: 'IOS_PROD_KEY' }));
      }
    } else if (isAndroidDevice) {
      android = androidProd || test || '';
      ios = test || '';
      if (!androidProd) {
        warnings.push(t('run.rc.fallbackToTest', { platform: 'Android', var: 'ANDROID_PROD_KEY' }));
      }
    } else {
      // Forced --rc=prod with no clear device: try both prod keys, fall back to test.
      ios = iosProd || test || '';
      android = androidProd || test || '';
      if (!iosProd && !androidProd && !test) {
        warnings.push(t('run.rc.forcedProdMissing'));
      }
    }
  }

  return { android, ios, mode: effectiveMode, warnings };
}

/**
 * Apply `replacements` to the dart-define list, replacing existing values
 * for the same key. Returns a new array; never mutates the input.
 */
function overrideDartDefines(defines, replacements) {
  const out = [];
  const seen = new Set(Object.keys(replacements));
  for (const def of defines) {
    const m = def.match(/^--dart-define=([^=]+)=/);
    if (m && seen.has(m[1])) continue;
    out.push(def);
  }
  for (const [key, value] of Object.entries(replacements)) {
    out.push(`--dart-define=${key}=${value}`);
  }
  return out;
}

async function runRun(directory, options = {}) {
  const t = createTranslator(options.language || detectDefaultLanguage());
  const projectDir = path.resolve(directory || '.');

  if (!(await fs.pathExists(path.join(projectDir, 'pubspec.yaml')))) {
    throw new Error(t('run.error.notFlutterProject'));
  }

  // Resolve device flag. If none of the platform shortcuts or -d is set,
  // ask the user when more than one device is available — `flutter run`
  // bails out with "More than one device connected" otherwise.
  const deviceArgs = [];
  let resolvedDeviceLabel = null;
  let pickedDevice = null;
  if (options.web) {
    deviceArgs.push('-d', 'chrome');
  } else if (options.ios) {
    deviceArgs.push('-d', 'ios');
  } else if (options.android) {
    deviceArgs.push('-d', 'android');
  } else if (options.device) {
    deviceArgs.push('-d', options.device);
  } else {
    const devices = listFlutterDevices(projectDir);
    if (devices.length > 1) {
      printCompactHeader(t);
      pickedDevice = await pickDevice(devices, t);
      if (!pickedDevice) {
        console.log(kleur.yellow(`  ⚠  ${t('run.warn.nothingSelected')}`));
        return;
      }
      deviceArgs.push('-d', pickedDevice.id);
      resolvedDeviceLabel = `${pickedDevice.name} (${pickedDevice.id})`;
    }
    // 0 or 1 device → let flutter handle it; it picks the only one or
    // prints its own "no devices" message.
  }

  // Read dart-defines from .vscode/launch.json (skip if --no-defines)
  let dartDefines = options.noDefines
    ? []
    : await readLaunchArgs(projectDir, options.prod);

  // Override RC keys based on device. We only touch RC_ANDROID_API_KEY /
  // RC_IOS_API_KEY when the launch.json already declares them — that's the
  // signal the project uses RevenueCat. Web defines (RC_WEB_API_KEY) aren't
  // touched here; they're already a single key.
  const usesRc = dartDefines.some(
    (a) => a.startsWith('--dart-define=RC_ANDROID_API_KEY=') ||
           a.startsWith('--dart-define=RC_IOS_API_KEY='),
  );
  let rcInfo = null;
  if (usesRc && !options.web) {
    const env = await loadEnvFile(projectDir);
    const mode = (options.rc || 'auto').toLowerCase();
    const deviceClass = classifyTargetDevice(projectDir, options, pickedDevice);
    rcInfo = resolveRcKeys(env, deviceClass, mode, t);
    if (rcInfo.mode !== 'legacy') {
      dartDefines = overrideDartDefines(dartDefines, {
        RC_ANDROID_API_KEY: rcInfo.android,
        RC_IOS_API_KEY: rcInfo.ios,
      });
    }
  }

  const args = ['run', ...deviceArgs, ...dartDefines];

  const envDefine = dartDefines.find((a) => a.startsWith('--dart-define=ENV='));
  const envValue = envDefine ? envDefine.split('=').pop() : null;
  const deviceLabel = options.web
    ? 'chrome'
    : options.ios
    ? 'ios'
    : options.android
    ? 'android'
    : options.device || resolvedDeviceLabel || null;
  const summaryParts = [];
  if (envValue) summaryParts.push(`ENV=${envValue}`);
  if (deviceLabel) summaryParts.push(`device: ${deviceLabel}`);
  const summary = summaryParts.length ? ` (${summaryParts.join(', ')})` : '';

  printCompactHeader(t);
  console.log(kleur.bold(`${t('run.launching')}${summary}`));
  if (rcInfo && rcInfo.mode !== 'legacy') {
    const mode = (options.rc || 'auto').toLowerCase();
    let label;
    if (mode === 'test') label = t('run.rc.forcedTest');
    else if (mode === 'prod') label = t('run.rc.forcedProd');
    else label = rcInfo.mode === 'prod' ? t('run.rc.usingProd') : t('run.rc.usingTest');
    console.log(kleur.dim(`  ✦ ${label}`));
    for (const w of rcInfo.warnings) {
      console.log(kleur.yellow(`  ⚠  ${w}`));
    }
  }
  console.log(kleur.dim(`  ✦ ${t('run.updateHint.prefix')} `) + kleur.cyan('kasy update') + kleur.dim(` ${t('run.updateHint.suffix')}\n`));

  try {
    await spawnFlutterWithSpinner(args, projectDir, t);
  } catch (err) {
    if (err.code === 'ENOENT') {
      throw new Error(t('run.error.flutterNotFound'));
    }
    throw err;
  }
}

module.exports = { runRun };
