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

import 'package:yaml/yaml.dart';

final RegExp _validBrandName = RegExp(r'^[a-z][a-z0-9_]*$');
const List<String> _defaultPreferredOrientations = <String>[
  'portraitUp',
  'portraitDown',
];
final Directory _repoRootDir = File.fromUri(Platform.script).parent.parent;
final String _currentDartExecutable = Platform.resolvedExecutable;
final String _flutterExecutable = _resolveFlutterExecutable();
final String _dartExecutable = _resolveDartExecutable();

Future<void> main(List<String> args) async {
  Directory.current = _repoRootDir.path;

  if (args.isEmpty) {
    print('\x1B[31m用法：dart run tool/generate_app.dart <品牌名>\x1B[0m');
    print('\x1B[33m示例：dart run tool/generate_app.dart aixue\x1B[0m');
    exit(1);
  }

  final brand = args.first;
  if (!_validBrandName.hasMatch(brand)) {
    print('\x1B[31m[错误] 品牌名 "$brand" 格式无效，仅允许小写字母、数字和下划线。\x1B[0m');
    exit(1);
  }

  final configFile = File('flavors/$brand.yaml');
  if (!configFile.existsSync()) {
    print('\x1B[31m[错误] 未找到配置文件：${configFile.path}\x1B[0m');
    exit(1);
  }

  print('\x1B[34m[信息] 正在为品牌生成应用：$brand...\x1B[0m');
  print('\x1B[34m[信息] Flutter 命令：$_flutterExecutable\x1B[0m');
  print('\x1B[34m[信息] Dart 命令：$_dartExecutable\x1B[0m');
  final yamlString = await configFile.readAsString();
  final config = loadYaml(yamlString) as YamlMap;

  final appName = config['app_name'] as String;
  final applicationId = config['application_id'] as String;
  final appKey = config['app_key'] as String;
  final defaultUrl = config['default_url'] as String?;
  final bootstrapConfigUrl = config['bootstrap_config_url'] as String?;
  final upgradeConfigUrl = config['upgrade_config_url'] as String?;
  final preferredOrientations = _readPreferredOrientations(
    config['preferred_orientations'],
  );

  final theme = config['theme'] as YamlMap;
  final accentColor = theme['accent_color'] as String;
  final bgColor = theme['bg_color'] as String;
  final textColor = theme['text_color'] as String;
  final mutedTextColor = theme['muted_text_color'] as String;

  final appDir = 'apps/$brand';

  await _createFlutterApp(brand, appDir, applicationId);
  await _addCoreDependency(appDir);
  await _rewriteAndroidPackageId(appDir, applicationId);
  await _overwriteMainActivity(appDir, applicationId);
  await _overwriteManifestLabel(appDir, appName);
  await _generateDartEntrypoint(
    appDir: appDir,
    appName: appName,
    appKey: appKey,
    accentColor: accentColor,
    backgroundColor: bgColor,
    textColor: textColor,
    mutedTextColor: mutedTextColor,
  );
  await _generateBootstrapConfig(
    appDir: appDir,
    defaultUrl: defaultUrl,
    preferredOrientations: preferredOrientations,
    bootstrapConfigUrl: bootstrapConfigUrl,
    upgradeConfigUrl: upgradeConfigUrl,
  );
  await _generateBrandingAssets(brand, appDir, config);
  await _registerFlutterAssets(appDir);
  await _configureSigning(appDir, config);

  print('\x1B[32m✔ 应用 $brand 已生成到 $appDir！\x1B[0m');
  print('\x1B[34m构建应用请执行：\x1B[0m');
  print('  cd $appDir');
  print('  ${_buildApkCommandForCurrentPlatform()}');
}

String _resolveFlutterExecutable() {
  final candidates = <String>[
    ..._toolCandidatesFromFlutterRoot(_flutterToolRelativePaths),
    ..._toolCandidatesFromRepo(_flutterToolRelativePaths),
    ..._flutterCandidatesFromCurrentDart(),
    'flutter',
  ];

  return _firstAvailableExecutable(candidates, fallback: 'flutter');
}

String _resolveDartExecutable() {
  final candidates = <String>[
    ..._toolCandidatesFromFlutterRoot(_dartToolRelativePaths),
    ..._toolCandidatesFromRepo(_dartToolRelativePaths),
    _currentDartExecutable,
    'dart',
  ];

  return _firstAvailableExecutable(candidates, fallback: 'dart');
}

List<String> _flutterCandidatesFromCurrentDart() {
  final dartExecutable = File(_currentDartExecutable);
  final dartBinDir = dartExecutable.parent;
  final dartSdkDir = dartBinDir.parent;
  final cacheDir = dartSdkDir.parent;
  final flutterBinDir = cacheDir.parent;

  if (_basename(dartSdkDir.path) != 'dart-sdk' ||
      _basename(cacheDir.path) != 'cache' ||
      !_isSameDirectoryName(flutterBinDir.path, 'bin')) {
    return <String>[];
  }

  final flutterBat = File.fromUri(flutterBinDir.uri.resolve('flutter.bat'));
  final flutterShell = File.fromUri(flutterBinDir.uri.resolve('flutter'));

  return <String>[
    if (flutterBat.existsSync()) flutterBat.path,
    if (flutterShell.existsSync()) flutterShell.path,
  ];
}

List<String> get _flutterToolRelativePaths => Platform.isWindows
    ? <String>['bin/flutter.bat', 'bin/flutter']
    : <String>['bin/flutter', 'bin/flutter.bat'];

List<String> get _dartToolRelativePaths => Platform.isWindows
    ? <String>[
        'bin/dart.bat',
        'bin/cache/dart-sdk/bin/dart.exe',
        'bin/dart',
        'bin/cache/dart-sdk/bin/dart',
      ]
    : <String>['bin/dart', 'bin/cache/dart-sdk/bin/dart'];

List<String> _toolCandidatesFromFlutterRoot(List<String> relativePaths) {
  final flutterRoot = Platform.environment['FLUTTER_ROOT'];
  if (flutterRoot == null || flutterRoot.isEmpty) {
    return <String>[];
  }

  return <String>[
    for (final relativePath in relativePaths)
      if (_fileExists(flutterRoot, relativePath))
        _joinPath(flutterRoot, relativePath),
  ];
}

List<String> _toolCandidatesFromRepo(List<String> relativePaths) {
  return <String>[
    for (final relativePath in relativePaths)
      if (_repoFile(relativePath).existsSync()) _repoFile(relativePath).path,
  ];
}

String _firstAvailableExecutable(
  List<String> candidates, {
  required String fallback,
}) {
  for (final candidate in candidates) {
    if (candidate == fallback || File(candidate).existsSync()) {
      return candidate;
    }
  }
  return fallback;
}

bool _fileExists(String? basePath, String childPath) {
  if (basePath == null || basePath.isEmpty) {
    return false;
  }
  return File(_joinPath(basePath, childPath)).existsSync();
}

String _joinPath(String basePath, String childPath) {
  final normalizedBase = basePath.replaceAll('\\', '/');
  final normalizedChild = childPath.replaceAll('\\', '/');
  return File.fromUri(
    Directory(normalizedBase).uri.resolve(normalizedChild),
  ).path;
}

File _repoFile(String relativePath) {
  return File.fromUri(_repoRootDir.uri.resolve(relativePath));
}

String _basename(String path) {
  final normalized = path
      .replaceAll('\\', '/')
      .replaceFirst(RegExp(r'/+$'), '');
  final segments = normalized.split('/');
  return segments.isEmpty ? normalized : segments.last;
}

bool _isSameDirectoryName(String path, String name) {
  return _basename(path).toLowerCase() == name.toLowerCase();
}

String _buildApkCommandForCurrentPlatform() {
  final fvmFlutter = _repoFile(
    Platform.isWindows
        ? '.fvm/flutter_sdk/bin/flutter.bat'
        : '.fvm/flutter_sdk/bin/flutter',
  );

  if (fvmFlutter.existsSync()) {
    final commandPath = Platform.isWindows
        ? '../../.fvm/flutter_sdk/bin/flutter.bat'
        : '../../.fvm/flutter_sdk/bin/flutter';
    return '$commandPath build apk';
  }

  return 'flutter build apk';
}

Future<ProcessResult> _runProcess(
  String executable,
  List<String> arguments, {
  String? workingDirectory,
}) async {
  try {
    return await Process.run(
      executable,
      arguments,
      workingDirectory: workingDirectory,
      runInShell:
          Platform.isWindows && executable.toLowerCase().endsWith('.bat'),
    );
  } on ProcessException catch (error) {
    print('\x1B[31m[错误] 外部命令启动失败：$executable\x1B[0m');
    print('\x1B[33m命令参数：${arguments.join(' ')}\x1B[0m');
    print('\x1B[33m系统信息：${error.message}\x1B[0m');

    if (executable == _flutterExecutable || executable == 'flutter') {
      print('\x1B[33m建议先执行：\x1B[0m');
      if (Platform.isWindows) {
        print(
          '  \$env:PATH = "${_repoFile('.fvm/flutter_sdk/bin').path};\$env:PATH"',
        );
      } else {
        print(
          '  export PATH="${_repoFile('.fvm/flutter_sdk/bin').path}:\$PATH"',
        );
      }
    }

    exit(1);
  }
}

List<String> _readPreferredOrientations(Object? raw) {
  if (raw is! YamlList && raw is! List) {
    return _defaultPreferredOrientations;
  }

  final values = <String>[];
  for (final item in (raw as List)) {
    final normalized = _normalizeOrientation(item?.toString());
    if (normalized != null && !values.contains(normalized)) {
      values.add(normalized);
    }
  }
  return values.isEmpty ? _defaultPreferredOrientations : values;
}

String? _normalizeOrientation(String? raw) {
  final value = raw?.trim();
  if (value == null || value.isEmpty) {
    return null;
  }
  return switch (value) {
    'portraitUp' || 'DeviceOrientation.portraitUp' => 'portraitUp',
    'portraitDown' || 'DeviceOrientation.portraitDown' => 'portraitDown',
    'landscapeLeft' || 'DeviceOrientation.landscapeLeft' => 'landscapeLeft',
    'landscapeRight' || 'DeviceOrientation.landscapeRight' => 'landscapeRight',
    _ => null,
  };
}

Future<void> _createFlutterApp(
  String brand,
  String appDir,
  String applicationId,
) async {
  print('\x1B[34m[信息] 正在执行 flutter create...\x1B[0m');
  final dir = Directory(appDir);
  if (dir.existsSync()) {
    print('\x1B[33m[警告] 目录 $appDir 已存在，正在清理...\x1B[0m');
    dir.deleteSync(recursive: true);
  }

  final segments = applicationId.split('.');
  final org = segments.sublist(0, segments.length - 1).join('.');
  final result = await _runProcess(_flutterExecutable, <String>[
    'create',
    '--org',
    org,
    '--project-name',
    brand,
    '--platforms',
    'android',
    '--android-language',
    'java',
    appDir,
  ]);

  if (result.exitCode != 0) {
    print('\x1B[31m[错误] flutter create 执行失败：\n${result.stderr}\x1B[0m');
    exit(1);
  }
}

Future<void> _addCoreDependency(String appDir) async {
  print('\x1B[34m[信息] 正在添加 web_shell_core 依赖...\x1B[0m');
  final result = await _runProcess(_flutterExecutable, <String>[
    'pub',
    'add',
    'web_shell_core',
    '--path',
    '../../packages/web_shell_core',
  ], workingDirectory: appDir);

  if (result.exitCode != 0) {
    print('\x1B[31m[错误] 添加依赖失败：\n${result.stderr}\x1B[0m');
    exit(1);
  }
}

Future<void> _rewriteAndroidPackageId(
  String appDir,
  String applicationId,
) async {
  print('\x1B[34m[信息] 正在校正 Android applicationId / namespace...\x1B[0m');
  final gradleFile = File('$appDir/android/app/build.gradle.kts');
  if (!gradleFile.existsSync()) {
    print('\x1B[31m[错误] 未找到 ${gradleFile.path}\x1B[0m');
    exit(1);
  }

  var content = await gradleFile.readAsString();
  content = content.replaceFirst(
    RegExp(r'namespace\s*=\s*"[^"]+"'),
    'namespace = "$applicationId"',
  );
  content = content.replaceFirst(
    RegExp(r'applicationId\s*=\s*"[^"]+"'),
    'applicationId = "$applicationId"',
  );
  await gradleFile.writeAsString(content);

  final javaDir = Directory('$appDir/android/app/src/main/java');
  if (javaDir.existsSync()) {
    javaDir.deleteSync(recursive: true);
  }
  await javaDir.create(recursive: true);
}

Future<void> _overwriteMainActivity(String appDir, String applicationId) async {
  print('\x1B[34m[信息] 正在注入 CoreShellActivity 继承关系...\x1B[0m');
  final mainActivityPath =
      '$appDir/android/app/src/main/java/${applicationId.replaceAll('.', '/')}/MainActivity.java';
  final javaContent =
      '''
package $applicationId;

import com.yuanxuan.webshell.core.web_shell_core.CoreShellActivity;

public class MainActivity extends CoreShellActivity {
}
''';

  final mainActivityFile = File(mainActivityPath);
  await mainActivityFile.parent.create(recursive: true);
  await mainActivityFile.writeAsString(javaContent);
}

Future<void> _overwriteManifestLabel(String appDir, String appName) async {
  print('\x1B[34m[信息] 正在更新 AndroidManifest.xml 应用名...\x1B[0m');
  final manifestFile = File('$appDir/android/app/src/main/AndroidManifest.xml');
  var content = await manifestFile.readAsString();
  content = content.replaceAll(
    RegExp(r'android:label="[^"]*"'),
    'android:label="$appName"',
  );
  await manifestFile.writeAsString(content);
}

Future<void> _generateDartEntrypoint({
  required String appDir,
  required String appName,
  required String appKey,
  required String accentColor,
  required String backgroundColor,
  required String textColor,
  required String mutedTextColor,
}) async {
  print('\x1B[34m[信息] 正在生成 lib/main.dart...\x1B[0m');
  final mainFile = File('$appDir/lib/main.dart');

  final dartContent =
      '''
import 'package:flutter/material.dart';
import 'package:web_shell_core/web_shell_core.dart';

void main() {
  runShellApp(
    ShellEnvironment(
      appName: '$appName',
      appKey: '$appKey',
      accentColor: const Color($accentColor),
      backgroundColor: const Color($backgroundColor),
      textColor: const Color($textColor),
      mutedTextColor: const Color($mutedTextColor),
      splashImage: const AssetImage('assets/branding/splash.png'),
      bootstrapConfigAsset: 'assets/config/bootstrap.json',
    ),
  );
}
''';

  await mainFile.writeAsString(dartContent);

  final defaultTestFile = File('$appDir/test/widget_test.dart');
  if (defaultTestFile.existsSync()) {
    await defaultTestFile.delete();
  }
}

Future<void> _generateBootstrapConfig({
  required String appDir,
  required String? defaultUrl,
  required List<String> preferredOrientations,
  String? bootstrapConfigUrl,
  String? upgradeConfigUrl,
}) async {
  print('\x1B[34m[信息] 正在生成默认启动配置 assets/config/bootstrap.json...\x1B[0m');
  final file = File('$appDir/assets/config/bootstrap.json');
  await file.parent.create(recursive: true);

  final data = <String, Object?>{
    if (defaultUrl != null && defaultUrl.trim().isNotEmpty)
      'initialUrl': defaultUrl.trim(),
    'preferredOrientations': preferredOrientations,
    if (bootstrapConfigUrl != null && bootstrapConfigUrl.trim().isNotEmpty)
      'bootstrapConfigUrl': bootstrapConfigUrl.trim(),
    if (upgradeConfigUrl != null && upgradeConfigUrl.trim().isNotEmpty)
      'upgradeConfigUrl': upgradeConfigUrl.trim(),
  };

  final content = const JsonEncoder.withIndent('  ').convert(data);
  await file.writeAsString('$content\n');
}

Future<void> _generateBrandingAssets(
  String brand,
  String appDir,
  YamlMap config,
) async {
  print('\x1B[34m[信息] 正在配置图标与启动页...\x1B[0m');
  if (config['branding'] == null) {
    print('\x1B[33m[警告] 配置中未找到 branding 段，跳过资源生成。\x1B[0m');
    return;
  }

  final branding = config['branding'] as YamlMap;
  final brandSourceDir = Directory('flavors/$brand');
  final brandTargetDir = Directory('$appDir/assets/branding');
  if (!brandSourceDir.existsSync()) {
    print('\x1B[31m[错误] 品牌资源目录不存在：${brandSourceDir.path}\x1B[0m');
    exit(1);
  }

  await brandTargetDir.create(recursive: true);
  for (final entity in brandSourceDir.listSync(recursive: true)) {
    if (entity is! File) {
      continue;
    }
    final relativePath = entity.path.substring(brandSourceDir.path.length + 1);
    final targetFile = File('${brandTargetDir.path}/$relativePath');
    await targetFile.parent.create(recursive: true);
    await entity.copy(targetFile.path);
  }

  print('\x1B[32m✔ 品牌资源已复制到 ${brandTargetDir.path}\x1B[0m');

  print('\x1B[34m[信息] 正在添加资源生成器依赖...\x1B[0m');
  final addDevDepsResult = await _runProcess(_flutterExecutable, <String>[
    'pub',
    'add',
    '--dev',
    'flutter_launcher_icons',
    'flutter_native_splash',
  ], workingDirectory: appDir);
  if (addDevDepsResult.exitCode != 0) {
    print('\x1B[31m[错误] 添加资源生成器依赖失败：\n${addDevDepsResult.stderr}\x1B[0m');
    exit(1);
  }

  await _runProcess(_flutterExecutable, <String>[
    'pub',
    'get',
  ], workingDirectory: appDir);

  final iconPath = 'assets/branding/${branding['icon']}';
  final iconForeground = 'assets/branding/${branding['icon_foreground']}';
  final iconBackground = branding['icon_background'] as String;
  final iconsYaml =
      '''
flutter_launcher_icons:
  android: true
  image_path: "$iconPath"
  adaptive_icon_background: "$iconBackground"
  adaptive_icon_foreground: "$iconForeground"
''';
  await File('$appDir/flutter_launcher_icons.yaml').writeAsString(iconsYaml);

  final splashPath = 'assets/branding/${branding['splash']}';
  final splashColor = branding['splash_color'] as String;
  final splashYaml =
      '''
flutter_native_splash:
  color: "$splashColor"
  image: "$splashPath"
  android_12:
    image: "$splashPath"
    icon_background_color: "$splashColor"
''';
  await File('$appDir/flutter_native_splash.yaml').writeAsString(splashYaml);

  print('\x1B[34m[信息] 正在生成应用图标...\x1B[0m');
  final iconsResult = await _runProcess(_dartExecutable, <String>[
    'run',
    'flutter_launcher_icons',
    '-f',
    'flutter_launcher_icons.yaml',
  ], workingDirectory: appDir);
  if (iconsResult.exitCode != 0) {
    print('\x1B[31m[错误] 图标生成失败：\n${iconsResult.stderr}\x1B[0m');
    print('\x1B[33mstdout:\n${iconsResult.stdout}\x1B[0m');
    exit(1);
  }

  print('\x1B[34m[信息] 正在生成启动页...\x1B[0m');
  final splashResult = await _runProcess(_dartExecutable, <String>[
    'run',
    'flutter_native_splash:create',
    '--path=flutter_native_splash.yaml',
  ], workingDirectory: appDir);
  if (splashResult.exitCode != 0) {
    print('\x1B[31m[错误] 启动页生成失败：\n${splashResult.stderr}\x1B[0m');
    print('\x1B[33mstdout:\n${splashResult.stdout}\x1B[0m');
    exit(1);
  }

  print('\x1B[34m[信息] 正在移除资源生成器依赖...\x1B[0m');
  await _runProcess(_flutterExecutable, <String>[
    'pub',
    'remove',
    'flutter_launcher_icons',
    'flutter_native_splash',
  ], workingDirectory: appDir);
}

Future<void> _registerFlutterAssets(String appDir) async {
  print('\x1B[34m[信息] 正在注册 Flutter assets...\x1B[0m');
  final pubspecFile = File('$appDir/pubspec.yaml');
  var content = await pubspecFile.readAsString();

  if (!RegExp(r'^\s+assets:\s*$', multiLine: true).hasMatch(content)) {
    content = content.replaceFirst(
      RegExp(r'^flutter:\s*\n', multiLine: true),
      'flutter:\n  assets:\n    - assets/branding/\n    - assets/config/\n',
    );
  } else {
    if (!content.contains('    - assets/branding/')) {
      content = content.replaceFirst(
        RegExp(r'^\s+assets:\s*\n', multiLine: true),
        '  assets:\n    - assets/branding/\n',
      );
    }
    if (!content.contains('    - assets/config/')) {
      content = content.replaceFirst(
        '    - assets/branding/\n',
        '    - assets/branding/\n    - assets/config/\n',
      );
    }
  }

  await pubspecFile.writeAsString(content);
}

Future<void> _configureSigning(String appDir, YamlMap config) async {
  print('\x1B[34m[信息] 正在配置应用签名...\x1B[0m');
  final signing = config['signing'] as YamlMap?;
  final keyAlias = signing?['key_alias'] as String? ?? 'my-key-alias';
  final keyPassword = signing?['key_password'] as String? ?? '123456';
  final storePassword = signing?['store_password'] as String? ?? '123456';
  final storeFile =
      signing?['store_file'] as String? ?? '../../../tool/key.jks';

  final keyPropsContent =
      '''
storePassword=$storePassword
keyPassword=$keyPassword
keyAlias=$keyAlias
storeFile=$storeFile
''';
  await File('$appDir/android/key.properties').writeAsString(keyPropsContent);

  final gradleFile = File('$appDir/android/app/build.gradle.kts');
  if (!gradleFile.existsSync()) {
    print('\x1B[33m[警告] 未找到 build.gradle.kts，跳过注入签名配置。\x1B[0m');
    return;
  }

  var content = await gradleFile.readAsString();
  if (!content.contains('val keystoreProperties = Properties()')) {
    content = content.replaceFirst('android {', '''
import java.io.File
import java.util.Properties
import java.io.FileInputStream

val keystoreProperties = Properties()
val keystorePropertiesFile = rootProject.file("key.properties")
if (keystorePropertiesFile.exists()) {
    FileInputStream(keystorePropertiesFile).use { keystoreProperties.load(it) }
}

val defaultReleaseStoreFile = rootProject.file("../../../tool/key.jks")
fun resolveKeystoreFile(rawPath: String?): File {
    val candidate = rawPath?.trim().orEmpty()
    if (candidate.isEmpty()) {
        return defaultReleaseStoreFile
    }

    val expandedHome = if (candidate.startsWith("~/") || candidate == "~") {
        candidate.replaceFirst("~", System.getProperty("user.home"))
    } else {
        candidate
    }

    val normalized = expandedHome.replace('\\', File.separatorChar).replace('/', File.separatorChar)
    val storeFile = File(normalized)
    return if (storeFile.isAbsolute) storeFile else rootProject.file(normalized)
}

val releaseStoreFile =
    resolveKeystoreFile(keystoreProperties["storeFile"] as String?)
val releaseKeyAlias = keystoreProperties["keyAlias"] as String? ?: "my-key-alias"
val releaseKeyPassword = keystoreProperties["keyPassword"] as String? ?: "123456"
val releaseStorePassword = keystoreProperties["storePassword"] as String? ?: "123456"

android {
''');
  }

  if (!content.contains('signingConfigs {\n        create("release") {')) {
    const newBuildTypes = '''
    signingConfigs {
        create("release") {
            keyAlias = releaseKeyAlias
            keyPassword = releaseKeyPassword
            storeFile = releaseStoreFile
            storePassword = releaseStorePassword
        }
    }

    buildTypes {
        getByName("release") {
            signingConfig = signingConfigs.getByName("release")
        }
    }
''';
    final buildTypeRegex = RegExp(
      r'\s*buildTypes\s*\{[\s\S]*?signingConfigs\.getByName\("debug"\)\s*\}\s*\}',
    );
    content = content.replaceFirst(buildTypeRegex, '\n$newBuildTypes');
  }

  await gradleFile.writeAsString(content);
}
