import 'dart:async';
import 'dart:convert';
import 'dart:io';

const _fvpDepsUrl =
    'https://github.com/wang-bin/mdk-sdk/releases/download/v0.37.0';
const _androidApplicationId = 'com.smplayer.smplayer';
const _debugApplicationId = '$_androidApplicationId.dev';
const _maxEmulatorWait = Duration(seconds: 120);
const _pollInterval = Duration(seconds: 3);

Future<void> main(List<String> arguments) async {
  try {
    final options = _Options.parse(arguments);
    if (options.help) {
      stdout.writeln(_usage);
      return;
    }
    exitCode = await _run(options);
  } on FormatException catch (error) {
    stderr.writeln('error: ${error.message}');
    stderr.writeln(_usage);
    exitCode = 2;
  } on ProcessException catch (error) {
    stderr.writeln('error: $error');
    exitCode = 1;
  } on StateError catch (error) {
    stderr.writeln('error: ${error.message}');
    exitCode = 1;
  }
}

Future<int> _run(_Options options) async {
  final stopwatch = Stopwatch()..start();
  final projectRoot = options.projectRoot ?? _defaultProjectRoot;
  final environment = _flutterEnvironment();
  var deviceId = options.device;

  if (deviceId != null) {
    stdout.writeln('[dev-android] 使用指定设备: $deviceId');
  } else {
    deviceId = await _androidDevice(projectRoot, environment);
    if (deviceId == null) {
      final emulatorId =
          options.emulator ?? await _firstEmulator(projectRoot, environment);
      if (emulatorId == null) {
        stderr
          ..writeln('[dev-android] 未找到 Android 设备，请:')
          ..writeln('              1. 连接真机（开启 USB 调试）')
          ..writeln(
            '              2. 或创建模拟器: '
            'flutter emulators --create --name MyEmulator',
          )
          ..writeln('              也可用 --device <id> 直接指定设备');
        return 1;
      }
      stdout
        ..writeln('[dev-android] 启动模拟器: $emulatorId')
        ..writeln('[dev-android] 等待模拟器启动...');
      await Process.start(
        'flutter',
        ['emulators', '--launch', emulatorId],
        workingDirectory: projectRoot,
        environment: environment,
        includeParentEnvironment: false,
        runInShell: Platform.isWindows,
        mode: ProcessStartMode.detached,
      );
      deviceId = await _waitForDevice(projectRoot, environment);
      if (deviceId == null) {
        stderr.writeln(
          '[dev-android] 模拟器启动超时 '
          '(${_maxEmulatorWait.inSeconds}s)，请手动启动后重试',
        );
        return 1;
      }
    }
    stdout.writeln('[dev-android] 目标设备: $deviceId');
  }

  if (_needsPubGet(projectRoot, options.force)) {
    stdout.writeln('[dev-android] 依赖有变更 -> flutter pub get');
    final pubExit = await _inheritFlutter(
      ['pub', 'get', if (options.offline) '--offline'],
      projectRoot,
      environment,
    );
    if (pubExit != 0) return pubExit;
  } else {
    stdout.writeln('[dev-android] 依赖未变更 -> 跳过 pub get');
  }

  final prepareFvpExit = await _prepareFvpAndroidDependencies(
    projectRoot,
    environment,
  );
  if (prepareFvpExit != 0) return prepareFvpExit;

  final appVersion = await _appVersion(projectRoot);
  final versionBuildNumber = await _appBuildNumber(projectRoot, appVersion);
  final installedBuildNumber = await _installedAppBuildNumber(
    projectRoot,
    deviceId,
    environment,
  );
  final appBuildNumber =
      installedBuildNumber != null && installedBuildNumber > versionBuildNumber
      ? installedBuildNumber
      : versionBuildNumber;
  stdout.writeln(
    '[dev-android] App version: $appVersion (android line), '
    'build number: $appBuildNumber',
  );
  if (installedBuildNumber != null &&
      installedBuildNumber > versionBuildNumber) {
    stdout.writeln(
      '[dev-android] 设备已安装更高版本号 $installedBuildNumber，'
      '开发包沿用该版本号以避免降级重装',
    );
  }
  environment['SMPLAYER_ANDROID_VERSION_CODE'] = '$appBuildNumber';
  final runArguments = [
    'run',
    '-d',
    deviceId,
    '--no-pub',
    ...options.flutterArguments,
  ];
  if (!_hasDartDefine(runArguments, 'SMPLAYER_VERSION')) {
    runArguments.add('--dart-define=SMPLAYER_VERSION=$appVersion');
  }

  stdout
    ..writeln('[dev-android] flutter ${runArguments.join(' ')}')
    ..writeln('[dev-android] 启动准备耗时: ${stopwatch.elapsedMilliseconds} ms');
  return _inheritFlutter(runArguments, projectRoot, environment);
}

Future<int> _prepareFvpAndroidDependencies(
  String projectRoot,
  Map<String, String> environment,
) async {
  final prepareScript = File(
    '${File.fromUri(Platform.script).parent.path}'
    '${Platform.pathSeparator}prepare_fvp_android_deps.dart',
  );
  final result = await Process.run(
    Platform.resolvedExecutable,
    [prepareScript.path, '--project-root', projectRoot],
    workingDirectory: projectRoot,
    environment: environment,
    includeParentEnvironment: false,
  );
  stdout.write(result.stdout);
  stderr.write(result.stderr);
  return result.exitCode;
}

Map<String, String> _flutterEnvironment() {
  final environment = Map<String, String>.of(Platform.environment)
    ..['FVP_DEPS_URL'] = _fvpDepsUrl
    ..remove('FVP_DEPS_LATEST');
  if (!Platform.isWindows) {
    if ((environment['FLUTTER_STORAGE_BASE_URL'] ?? '').isEmpty) {
      environment['FLUTTER_STORAGE_BASE_URL'] = 'https://storage.flutter-io.cn';
    }
    if ((environment['PUB_HOSTED_URL'] ?? '').isEmpty) {
      environment['PUB_HOSTED_URL'] = 'https://pub.flutter-io.cn';
    }
  }
  return environment;
}

bool _needsPubGet(String projectRoot, bool force) {
  if (force) return true;
  final packageConfig = File(
    '$projectRoot${Platform.pathSeparator}.dart_tool'
    '${Platform.pathSeparator}package_config.json',
  );
  final pubspec = File('$projectRoot${Platform.pathSeparator}pubspec.yaml');
  if (!packageConfig.existsSync() || !pubspec.existsSync()) return true;
  final configTime = packageConfig.lastModifiedSync();
  if (pubspec.lastModifiedSync().isAfter(configTime)) return true;
  final lock = File('$projectRoot${Platform.pathSeparator}pubspec.lock');
  return lock.existsSync() && lock.lastModifiedSync().isAfter(configTime);
}

Future<String?> _androidDevice(
  String projectRoot,
  Map<String, String> environment,
) async {
  final result = await _flutter(
    ['devices', '--machine'],
    projectRoot,
    environment,
  );
  if (result.exitCode != 0) return null;
  try {
    final devices = jsonDecode('${result.stdout}');
    if (devices is! List) return null;
    for (final device in devices) {
      if (device is Map &&
          '${device['targetPlatform']}'.startsWith('android')) {
        final id = '${device['id']}';
        if (id.isNotEmpty && id != 'null') return id;
      }
    }
  } on FormatException {
    return null;
  }
  return null;
}

Future<String?> _firstEmulator(
  String projectRoot,
  Map<String, String> environment,
) async {
  final result = await _flutter(['emulators'], projectRoot, environment);
  if (result.exitCode != 0) return null;
  final emulatorPattern = RegExp(
    r'^\s*(\S+)\s+.*\bandroid\b',
    caseSensitive: false,
  );
  for (final line in '${result.stdout}'.split(RegExp(r'\r?\n'))) {
    final match = emulatorPattern.firstMatch(line);
    if (match != null) return match[1];
  }
  return null;
}

Future<String?> _waitForDevice(
  String projectRoot,
  Map<String, String> environment,
) async {
  for (
    var waited = _pollInterval;
    waited <= _maxEmulatorWait;
    waited += _pollInterval
  ) {
    await Future<void>.delayed(_pollInterval);
    final deviceId = await _androidDevice(projectRoot, environment);
    if (deviceId != null) return deviceId;
    stdout.writeln('[dev-android] 等待中... (${waited.inSeconds}s)');
  }
  return null;
}

Future<String> _appVersion(String projectRoot) async {
  final result = await _runVersionCommand(projectRoot, ['current', 'android']);
  return '${result.stdout}'.trim();
}

Future<int> _appBuildNumber(String projectRoot, String appVersion) async {
  final result = await _runVersionCommand(projectRoot, [
    'build-number',
    appVersion,
  ]);
  final buildNumber = int.tryParse('${result.stdout}'.trim());
  if (buildNumber == null || buildNumber <= 0) {
    throw StateError('Invalid Android build number: ${result.stdout}');
  }
  return buildNumber;
}

Future<int?> _installedAppBuildNumber(
  String projectRoot,
  String deviceId,
  Map<String, String> environment,
) async {
  final adbExecutable = _androidDebugBridgeExecutable(projectRoot, environment);
  try {
    final result = await Process.run(
      adbExecutable,
      ['-s', deviceId, 'shell', 'dumpsys', 'package', _debugApplicationId],
      workingDirectory: projectRoot,
      environment: environment,
      includeParentEnvironment: false,
      runInShell: Platform.isWindows,
    );
    if (result.exitCode != 0) return null;
    final versionCodeMatch = RegExp(
      r'\bversionCode=(\d+)\b',
    ).firstMatch('${result.stdout}');
    return versionCodeMatch == null
        ? null
        : int.tryParse(versionCodeMatch.group(1)!);
  } on ProcessException {
    return null;
  }
}

String _androidDebugBridgeExecutable(
  String projectRoot,
  Map<String, String> environment,
) {
  final sdkDirectories = <String?>[
    environment['ANDROID_SDK_ROOT'],
    environment['ANDROID_HOME'],
    _androidSdkDirectoryFromLocalProperties(projectRoot),
    if (Platform.isWindows && environment['LOCALAPPDATA'] != null)
      '${environment['LOCALAPPDATA']}${Platform.pathSeparator}Android'
          '${Platform.pathSeparator}sdk',
  ];
  final executableName = Platform.isWindows ? 'adb.exe' : 'adb';
  for (final sdkDirectory in sdkDirectories) {
    if (sdkDirectory == null || sdkDirectory.trim().isEmpty) continue;
    final executable = File(
      '${sdkDirectory.trim()}${Platform.pathSeparator}platform-tools'
      '${Platform.pathSeparator}$executableName',
    );
    if (executable.existsSync()) return executable.path;
  }
  return executableName;
}

String? _androidSdkDirectoryFromLocalProperties(String projectRoot) {
  final propertiesFile = File(
    '$projectRoot${Platform.pathSeparator}android'
    '${Platform.pathSeparator}local.properties',
  );
  if (!propertiesFile.existsSync()) return null;
  for (final line in propertiesFile.readAsLinesSync()) {
    final trimmedLine = line.trim();
    if (!trimmedLine.startsWith('sdk.dir=')) continue;
    return trimmedLine
        .substring('sdk.dir='.length)
        .replaceAll(r'\:', ':')
        .replaceAll(r'\\', r'\');
  }
  return null;
}

Future<ProcessResult> _runVersionCommand(
  String projectRoot,
  List<String> arguments,
) async {
  final result = await Process.run(Platform.resolvedExecutable, [
    '${File.fromUri(Platform.script).parent.path}'
        '${Platform.pathSeparator}version.dart',
    ...arguments,
    '--project-root',
    projectRoot,
  ]);
  if (result.exitCode != 0) {
    throw StateError('${result.stderr}'.trim());
  }
  return result;
}

bool _hasDartDefine(List<String> arguments, String name) {
  var previous = '';
  for (final argument in arguments) {
    if (argument.startsWith('--dart-define=$name=') ||
        (previous == '--dart-define' && argument.startsWith('$name='))) {
      return true;
    }
    previous = argument;
  }
  return false;
}

Future<ProcessResult> _flutter(
  List<String> arguments,
  String projectRoot,
  Map<String, String> environment,
) {
  return Process.run(
    'flutter',
    arguments,
    workingDirectory: projectRoot,
    environment: environment,
    includeParentEnvironment: false,
    runInShell: Platform.isWindows,
  );
}

Future<int> _inheritFlutter(
  List<String> arguments,
  String projectRoot,
  Map<String, String> environment,
) async {
  final process = await Process.start(
    'flutter',
    arguments,
    workingDirectory: projectRoot,
    environment: environment,
    includeParentEnvironment: false,
    runInShell: Platform.isWindows,
    mode: ProcessStartMode.inheritStdio,
  );
  return process.exitCode;
}

final class _Options {
  const _Options({
    this.device,
    this.emulator,
    this.projectRoot,
    this.force = false,
    this.offline = false,
    this.help = false,
    this.flutterArguments = const [],
  });

  factory _Options.parse(List<String> arguments) {
    String? device;
    String? emulator;
    String? projectRoot;
    var force = false;
    var offline = false;
    var help = false;
    var flutterArguments = <String>[];

    for (var index = 0; index < arguments.length; index++) {
      final argument = arguments[index];
      switch (argument.toLowerCase()) {
        case '--device' || '-device':
          device = _valueAfter(arguments, ++index, argument);
        case '--emulator' || '-emulator':
          emulator = _valueAfter(arguments, ++index, argument);
        case '--project-root':
          projectRoot = _valueAfter(arguments, ++index, argument);
        case '--force' || '-force':
          force = true;
        case '--offline' || '-offline':
          offline = true;
        case '-h' || '--help':
          help = true;
        case '--' || '-flutterargs':
          flutterArguments = arguments.skip(index + 1).toList();
          index = arguments.length;
        default:
          throw FormatException("Unknown argument '$argument'.");
      }
    }
    return _Options(
      device: device,
      emulator: emulator,
      projectRoot: projectRoot,
      force: force,
      offline: offline,
      help: help,
      flutterArguments: flutterArguments,
    );
  }

  final String? device;
  final String? emulator;
  final String? projectRoot;
  final bool force;
  final bool offline;
  final bool help;
  final List<String> flutterArguments;
}

String _valueAfter(List<String> arguments, int index, String option) {
  if (index >= arguments.length) {
    throw FormatException('Missing value for $option.');
  }
  return arguments[index];
}

String get _defaultProjectRoot =>
    File.fromUri(Platform.script).parent.parent.path;

const _usage = '''Usage:
  dart scripts/dev_android.dart [--device <id>] [--emulator <id>]
      [--force] [--offline] [-- <flutter run args>]

PowerShell aliases -Device, -Emulator, -Force, -Offline, and -FlutterArgs
remain supported.''';
