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

import 'package:archive/archive.dart';
import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_app_update/azhon_app_update.dart';
import 'package:flutter_app_update/update_model.dart';
import 'package:island/widgets/content/markdown.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as path;
import 'package:process_run/process_run.dart';
import 'package:collection/collection.dart'; // Added for firstWhereOrNull
import 'package:styled_widget/styled_widget.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:island/widgets/content/sheet.dart';
import 'package:island/talker.dart';

/// Data model for a GitHub release we care about
class GithubReleaseInfo {
  final String tagName;
  final String name;
  final String body;
  final String htmlUrl;
  final DateTime createdAt;
  final List<GithubReleaseAsset> assets;

  const GithubReleaseInfo({
    required this.tagName,
    required this.name,
    required this.body,
    required this.htmlUrl,
    required this.createdAt,
    this.assets = const [],
  });
}

/// Data model for a GitHub release asset
class GithubReleaseAsset {
  final String name;
  final String browserDownloadUrl;

  const GithubReleaseAsset({
    required this.name,
    required this.browserDownloadUrl,
  });

  factory GithubReleaseAsset.fromJson(Map<String, dynamic> json) {
    return GithubReleaseAsset(
      name: json['name'] as String,
      browserDownloadUrl: json['browser_download_url'] as String,
    );
  }
}

/// Parses version and build number from "x.y.z+build"
class _ParsedVersion implements Comparable<_ParsedVersion> {
  final int major;
  final int minor;
  final int patch;
  final int build;

  const _ParsedVersion(this.major, this.minor, this.patch, this.build);

  static _ParsedVersion? tryParse(String input) {
    // Expect format like 0.0.0+00 (build after '+'). Allow missing build as 0.
    final partsPlus = input.split('+');
    final core = partsPlus[0].trim();
    final buildStr = partsPlus.length > 1 ? partsPlus[1].trim() : '0';
    final coreParts = core.split('.');
    if (coreParts.length != 3) return null;

    final major = int.tryParse(coreParts[0]) ?? 0;
    final minor = int.tryParse(coreParts[1]) ?? 0;
    final patch = int.tryParse(coreParts[2]) ?? 0;
    final build = int.tryParse(buildStr) ?? 0;

    return _ParsedVersion(major, minor, patch, build);
  }

  /// Normalize Android build numbers by removing architecture-based offsets
  /// Android adds 1000 for x86, 2000 for ARMv7, 4000 for ARMv8
  int get normalizedBuild {
    // Check if build number has an architecture offset
    // We detect this by checking if the build % 1000 is the base build
    if (build >= 4000) {
      // Likely ARMv8 (arm64-v8a) with +4000 offset
      return build % 4000;
    } else if (build >= 2000) {
      // Likely ARMv7 (armeabi-v7a) with +2000 offset
      return build % 2000;
    } else if (build >= 1000) {
      // Likely x86/x86_64 with +1000 offset
      return build % 1000;
    }
    // No offset, return as-is
    return build;
  }

  @override
  int compareTo(_ParsedVersion other) {
    if (major != other.major) return major.compareTo(other.major);
    if (minor != other.minor) return minor.compareTo(other.minor);
    if (patch != other.patch) return patch.compareTo(other.patch);
    // Use normalized build numbers for comparison to handle Android arch offsets
    return normalizedBuild.compareTo(other.normalizedBuild);
  }

  @override
  String toString() => '$major.$minor.$patch+$build';
}

class UpdateService {
  UpdateService({Dio? dio, this.useProxy = false})
    : _dio =
          dio ??
          Dio(
            BaseOptions(
              headers: {
                // Identify the app to GitHub; avoids some rate-limits and adds clarity
                'Accept': 'application/vnd.github+json',
                'User-Agent': 'solian-update-checker',
              },
              connectTimeout: const Duration(seconds: 10),
              receiveTimeout: const Duration(seconds: 15),
            ),
          );

  final Dio _dio;
  final bool useProxy;

  static const _proxyBaseUrl = 'https://ghfast.top/';

  static const _releasesLatestApi =
      'https://api.github.com/repos/solsynth/solian/releases/latest';

  /// Checks GitHub for the latest release and compares against the current app version.
  /// If update is available, shows a bottom sheet with changelog and an action to open release page.
  Future<void> checkForUpdates(BuildContext context) async {
    talker.info('[Update] Checking for updates...');
    try {
      final release = await fetchLatestRelease();
      if (release == null) {
        talker.info('[Update] No latest release found or could not fetch.');
        return;
      }
      talker.info('[Update] Fetched latest release: ${release.tagName}');

      final info = await PackageInfo.fromPlatform();
      final localVersionStr = '${info.version}+${info.buildNumber}';
      talker.info('[Update] Local app version: $localVersionStr');

      final latest = _ParsedVersion.tryParse(release.tagName);
      final local = _ParsedVersion.tryParse(localVersionStr);

      if (latest == null || local == null) {
        talker.info(
          '[Update] Failed to parse versions. Latest: ${release.tagName}, Local: $localVersionStr',
        );
        // If parsing fails, do nothing silently
        return;
      }
      talker.info('[Update] Parsed versions. Latest: $latest, Local: $local');

      final needsUpdate = latest.compareTo(local) > 0;
      if (!needsUpdate) {
        talker.info('[Update] App is up to date. No update needed.');
        return;
      }
      talker.info('[Update] Update available! Latest: $latest, Local: $local');

      if (!context.mounted) {
        talker.info('[Update] Context not mounted, cannot show update sheet.');
        return;
      }

      // Delay to ensure UI is ready (if called at startup)
      await Future.delayed(const Duration(milliseconds: 100));

      if (context.mounted) {
        await showUpdateSheet(context, release);
        talker.info('[Update] Update sheet shown.');
      }
    } catch (e) {
      talker.error('[Update] Error checking for updates: $e');
      // Ignore errors (network, api, etc.)
      return;
    }
  }

  /// Manually show the update sheet with a provided release.
  /// Useful for About page or testing.
  Future<void> showUpdateSheet(
    BuildContext context,
    GithubReleaseInfo release,
  ) async {
    if (!context.mounted) return;
    await showModalBottomSheet(
      context: context,
      isScrollControlled: true,
      useRootNavigator: true,
      builder: (ctx) {
        String? androidUpdateUrl;
        String? windowsUpdateUrl;
        if (Platform.isAndroid) {
          androidUpdateUrl = _getAndroidUpdateUrl(release.assets);
        }
        if (Platform.isWindows) {
          windowsUpdateUrl = _getWindowsUpdateUrl();
        }
        return _UpdateSheet(
          release: release,
          onOpen: () async {
            final uri = Uri.parse(release.htmlUrl);
            if (await canLaunchUrl(uri)) {
              await launchUrl(uri, mode: LaunchMode.externalApplication);
            }
          },
          androidUpdateUrl: androidUpdateUrl,
          windowsUpdateUrl: windowsUpdateUrl,
          useProxy: useProxy, // Pass the useProxy flag
        );
      },
    );
  }

  String? _getAndroidUpdateUrl(List<GithubReleaseAsset> assets) {
    final arm64 = assets.firstWhereOrNull(
      (asset) => asset.name == 'app-arm64-v8a-release.apk',
    );
    final armeabi = assets.firstWhereOrNull(
      (asset) => asset.name == 'app-armeabi-v7a-release.apk',
    );
    final x86_64 = assets.firstWhereOrNull(
      (asset) => asset.name == 'app-x86_64-release.apk',
    );

    // Prioritize arm64, then armeabi, then x86_64
    if (arm64 != null) {
      return 'https://fs.solsynth.dev/d/official/solian/${arm64.name}';
    } else if (armeabi != null) {
      return 'https://fs.solsynth.dev/d/official/solian/${armeabi.name}';
    } else if (x86_64 != null) {
      return 'https://fs.solsynth.dev/d/official/solian/${x86_64.name}';
    }
    return null;
  }

  String _getWindowsUpdateUrl() {
    return 'https://fs.solsynth.dev/d/official/solian/build-output-windows-installer.zip';
  }

  /// Performs automatic Windows update: download, extract, and install
  Future<void> performAutomaticWindowsUpdate(
    BuildContext context,
    String url,
  ) async {
    if (!context.mounted) return;

    showDialog(
      context: context,
      barrierDismissible: false,
      builder:
          (context) => _WindowsUpdateDialog(
            updateUrl: url,
            onComplete: () {
              // Close the update sheet
              Navigator.of(context).pop();
            },
          ),
    );
  }

  /// Fetch the latest release info from GitHub.
  /// Public so other screens (e.g., About) can manually trigger update checks.
  Future<GithubReleaseInfo?> fetchLatestRelease() async {
    final apiEndpoint =
        useProxy
            ? '$_proxyBaseUrl${Uri.encodeComponent(_releasesLatestApi)}'
            : _releasesLatestApi;

    talker.info(
      '[Update] Fetching latest release from GitHub API: $apiEndpoint (Proxy: $useProxy)',
    );
    final resp = await _dio.get(apiEndpoint);
    if (resp.statusCode != 200) {
      talker.error(
        '[Update] Failed to fetch latest release. Status code: ${resp.statusCode}',
      );
      return null;
    }
    final data = resp.data as Map<String, dynamic>;
    talker.info('[Update] Successfully fetched release data.');

    final tagName = (data['tag_name'] ?? '').toString();
    final name = (data['name'] ?? tagName).toString();
    final body = (data['body'] ?? '').toString();
    final htmlUrl = (data['html_url'] ?? '').toString();
    final createdAtStr = (data['created_at'] ?? '').toString();
    final createdAt = DateTime.tryParse(createdAtStr) ?? DateTime.now();
    final assetsData =
        (data['assets'] as List<dynamic>?)
            ?.map((e) => GithubReleaseAsset.fromJson(e as Map<String, dynamic>))
            .toList() ??
        [];

    if (tagName.isEmpty || htmlUrl.isEmpty) {
      talker.error(
        '[Update] Missing tag_name or html_url in release data. TagName: "$tagName", HtmlUrl: "$htmlUrl"',
      );
      return null;
    }

    talker.info('[Update] Returning GithubReleaseInfo for tag: $tagName');
    return GithubReleaseInfo(
      tagName: tagName,
      name: name,
      body: body,
      htmlUrl: htmlUrl,
      createdAt: createdAt,
      assets: assetsData,
    );
  }
}

class _WindowsUpdateDialog extends StatefulWidget {
  const _WindowsUpdateDialog({
    required this.updateUrl,
    required this.onComplete,
  });

  final String updateUrl;
  final VoidCallback onComplete;

  @override
  State<_WindowsUpdateDialog> createState() => _WindowsUpdateDialogState();
}

class _WindowsUpdateDialogState extends State<_WindowsUpdateDialog> {
  final ValueNotifier<double?> progressNotifier = ValueNotifier<double?>(null);
  final ValueNotifier<String> messageNotifier = ValueNotifier<String>(
    'Downloading installer...',
  );

  @override
  void initState() {
    super.initState();
    _startUpdate();
  }

  Future<void> _startUpdate() async {
    try {
      // Step 1: Download
      final zipPath = await _downloadWindowsInstaller(
        widget.updateUrl,
        onProgress: (received, total) {
          if (total == -1) {
            progressNotifier.value = null;
          } else {
            progressNotifier.value = received / total;
          }
        },
      );
      if (zipPath == null) {
        _showError('Failed to download installer');
        return;
      }

      // Step 2: Extract
      messageNotifier.value = 'Extracting installer...';
      progressNotifier.value = null; // Indeterminate for extraction

      final extractDir = await _extractWindowsInstaller(zipPath);
      if (extractDir == null) {
        _showError('Failed to extract installer');
        return;
      }

      // Step 3: Run installer
      messageNotifier.value = 'Running installer...';

      final success = await _runWindowsInstaller(extractDir);
      if (!mounted) return;

      if (success) {
        messageNotifier.value = 'Update Complete';
        progressNotifier.value = 1.0;
        await Future.delayed(const Duration(seconds: 2));
        if (mounted) {
          Navigator.of(context).pop();
          widget.onComplete();
        }
      } else {
        _showError('Failed to run installer');
      }

      // Cleanup
      try {
        await File(zipPath).delete();
        await Directory(extractDir).delete(recursive: true);
      } catch (e) {
        talker.error('[Update] Error cleaning up temporary files: $e');
      }
    } catch (e) {
      _showError('Update failed: $e');
    }
  }

  void _showError(String message) {
    if (!mounted) return;
    Navigator.of(context).pop();
    showDialog(
      context: context,
      builder:
          (context) => AlertDialog(
            title: const Text('Update Failed'),
            content: Text(message),
            actions: [
              TextButton(
                onPressed: () => Navigator.of(context).pop(),
                child: const Text('OK'),
              ),
            ],
          ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return AlertDialog(
      title: const Text('Installing Update'),
      content: Column(
        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          ValueListenableBuilder<double?>(
            valueListenable: progressNotifier,
            builder: (context, progress, child) {
              return LinearProgressIndicator(value: progress);
            },
          ),
          const SizedBox(height: 16),
          ValueListenableBuilder<String>(
            valueListenable: messageNotifier,
            builder: (context, message, child) {
              return Text(message);
            },
          ),
        ],
      ),
    );
  }

  /// Downloads the Windows installer ZIP file
  Future<String?> _downloadWindowsInstaller(
    String url, {
    void Function(int received, int total)? onProgress,
  }) async {
    try {
      talker.info('[Update] Starting Windows installer download from: $url');

      final tempDir = await getTemporaryDirectory();
      final fileName =
          'solian-installer-${DateTime.now().millisecondsSinceEpoch}.zip';
      final filePath = path.join(tempDir.path, fileName);

      final response = await Dio().download(
        url,
        filePath,
        onReceiveProgress: (received, total) {
          if (total != -1) {
            talker.info(
              '[Update] Download progress: ${(received / total * 100).toStringAsFixed(1)}%',
            );
          }
          onProgress?.call(received, total);
        },
      );

      if (response.statusCode == 200) {
        talker.info(
          '[Update] Windows installer downloaded successfully to: $filePath',
        );
        return filePath;
      } else {
        talker.error(
          '[Update] Failed to download Windows installer. Status: ${response.statusCode}',
        );
        return null;
      }
    } catch (e) {
      talker.error('[Update] Error downloading Windows installer: $e');
      return null;
    }
  }

  /// Extracts the ZIP file to a temporary directory
  Future<String?> _extractWindowsInstaller(String zipPath) async {
    try {
      talker.info('[Update] Extracting Windows installer from: $zipPath');

      final tempDir = await getTemporaryDirectory();
      final extractDir = path.join(
        tempDir.path,
        'solian-installer-${DateTime.now().millisecondsSinceEpoch}',
      );

      final zipFile = File(zipPath);
      final bytes = await zipFile.readAsBytes();
      final archive = ZipDecoder().decodeBytes(bytes);

      for (final file in archive) {
        final filename = file.name;
        if (file.isFile) {
          final data = file.content as List<int>;
          final filePath = path.join(extractDir, filename);
          await Directory(path.dirname(filePath)).create(recursive: true);
          await File(filePath).writeAsBytes(data);
        } else {
          final dirPath = path.join(extractDir, filename);
          await Directory(dirPath).create(recursive: true);
        }
      }

      talker.info(
        '[Update] Windows installer extracted successfully to: $extractDir',
      );
      return extractDir;
    } catch (e) {
      talker.error('[Update] Error extracting Windows installer: $e');
      return null;
    }
  }

  /// Runs the setup.exe file
  Future<bool> _runWindowsInstaller(String extractDir) async {
    try {
      talker.info('[Update] Running Windows installer from: $extractDir');

      final dir = Directory(extractDir);
      final exeFiles =
          dir
              .listSync()
              .where((f) => f is File && f.path.endsWith('.exe'))
              .toList();

      if (exeFiles.isEmpty) {
        talker.info('[Update] No .exe file found in extracted directory');
        return false;
      }

      final setupExePath = exeFiles.first.path;
      talker.info('[Update] Found installer executable: $setupExePath');

      final shell = Shell();
      final results = await shell.run(setupExePath);
      final result = results.first;

      if (result.exitCode == 0) {
        talker.info('[Update] Windows installer completed successfully');
        return true;
      } else {
        talker.error(
          '[Update] Windows installer failed with exit code: ${result.exitCode}',
        );
        talker.error('[Update] Installer output: ${result.stdout}');
        talker.error('[Update] Installer errors: ${result.stderr}');
        return false;
      }
    } catch (e) {
      talker.error('[Update] Error running Windows installer: $e');
      return false;
    }
  }
}

class _UpdateSheet extends StatefulWidget {
  const _UpdateSheet({
    required this.release,
    required this.onOpen,
    this.androidUpdateUrl,
    this.windowsUpdateUrl,
    this.useProxy = false,
  });

  final String? androidUpdateUrl;
  final String? windowsUpdateUrl;
  final bool useProxy;
  final GithubReleaseInfo release;
  final VoidCallback onOpen;

  @override
  State<_UpdateSheet> createState() => _UpdateSheetState();
}

class _UpdateSheetState extends State<_UpdateSheet> {
  late bool _useProxy;

  @override
  void initState() {
    super.initState();
    _useProxy = widget.useProxy;
  }

  Future<void> _installUpdate(String url) async {
    String downloadUrl = url;
    if (_useProxy) {
      final fileName = url.split('/').last;
      downloadUrl = 'https://fs.solsynth.dev/d/rainyun02/solian/$fileName';
    }

    UpdateModel model = UpdateModel(
      downloadUrl,
      "solian-update-${widget.release.tagName}.apk",
      "launcher_icon",
      'https://apps.apple.com/us/app/solian/id6499032345',
    );
    AzhonAppUpdate.update(model);
  }

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    return SheetScaffold(
      titleText: 'updateAvailable'.tr(),
      child: Padding(
        padding: EdgeInsets.only(
          bottom: 16 + MediaQuery.of(context).padding.bottom,
        ),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  widget.release.name,
                  style: theme.textTheme.titleMedium,
                ).bold(),
                Text(widget.release.tagName).fontSize(12),
              ],
            ).padding(vertical: 16, horizontal: 16),
            const Divider(height: 1),
            Expanded(
              child: SingleChildScrollView(
                padding: const EdgeInsets.symmetric(
                  horizontal: 16,
                  vertical: 16,
                ),
                child: MarkdownTextContent(
                  content:
                      widget.release.body.isEmpty
                          ? 'noChangelogProvided'.tr()
                          : widget.release.body,
                ),
              ),
            ),
            if (!kIsWeb && Platform.isAndroid)
              SwitchListTile(
                title: Text('useSecondarySourceForDownload'.tr()),
                value: _useProxy,
                onChanged: (value) {
                  setState(() {
                    _useProxy = value;
                  });
                },
              ).padding(horizontal: 8),
            Column(
              children: [
                Row(
                  spacing: 8,
                  children: [
                    if (!kIsWeb &&
                        Platform.isAndroid &&
                        widget.androidUpdateUrl != null)
                      Expanded(
                        child: FilledButton.icon(
                          onPressed: () {
                            talker.info(widget.androidUpdateUrl!);
                            _installUpdate(widget.androidUpdateUrl!);
                          },
                          icon: const Icon(Symbols.update),
                          label: Text('installUpdate'.tr()),
                        ),
                      ),
                    if (!kIsWeb &&
                        Platform.isWindows &&
                        widget.windowsUpdateUrl != null)
                      Expanded(
                        child: FilledButton.icon(
                          onPressed: () {
                            // Access the UpdateService instance to call the automatic update method
                            final updateService = UpdateService(
                              useProxy: widget.useProxy,
                            );
                            updateService.performAutomaticWindowsUpdate(
                              context,
                              widget.windowsUpdateUrl!,
                            );
                          },
                          icon: const Icon(Symbols.update),
                          label: Text('installUpdate'.tr()),
                        ),
                      ),
                    Expanded(
                      child: FilledButton.icon(
                        onPressed: widget.onOpen,
                        icon: const Icon(Icons.open_in_new),
                        label: Text('openReleasePage'.tr()),
                      ),
                    ),
                  ],
                ),
              ],
            ).padding(horizontal: 16),
          ],
        ),
      ),
    );
  }
}
