import 'dart:io';
import 'package:archive/archive.dart';
import 'package:crypto/crypto.dart';
import 'package:dio/dio.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as path;

import '../app/data/models/launch/smart_geo.dart';
import 'log/logger.dart';

/// 地理数据下载工具类
class GeoDownloader {
  static const String TAG = 'GeoDownloader';
  static final GeoDownloader _instance = GeoDownloader._internal();
  factory GeoDownloader() => _instance;
  GeoDownloader._internal();

  final Dio _dio = Dio(
    BaseOptions(
      connectTimeout: const Duration(seconds: 30),
      receiveTimeout: const Duration(minutes: 5),
      sendTimeout: const Duration(seconds: 30),
    ),
  );

  /// 获取 geo 文件目录（包名/files/geo）
  Future<Directory> _getGeoDirectory() async {
    final appDir = await getFilesDir();
    // 使用 files/geo 路径
    final geoDir = Directory(path.join(appDir.path, 'geo'));
    if (!await geoDir.exists()) {
      await geoDir.create(recursive: true);
    }
    return geoDir;
  }

  /// 获取文件目录
  Future<Directory> getFilesDir() async {
    if (Platform.isAndroid || Platform.isWindows) {
      return await getApplicationSupportDirectory();
    } else {
      // iOS fallback
      return await getApplicationDocumentsDirectory();
    }
  }

  /// 计算文件的 MD5
  Future<String?> _calculateFileMd5(String filePath) async {
    try {
      final file = File(filePath);
      if (!await file.exists()) {
        return null;
      }
      final bytes = await file.readAsBytes();
      final digest = md5.convert(bytes);
      return digest.toString();
    } catch (e) {
      log(TAG, 'Error calculating MD5: $e');
      return null;
    }
  }

  /// 下载并解压 zip 文件
  /// [url] 下载地址
  /// [expectedMd5] 期望的 MD5 值（zip 文件的 MD5）
  /// [targetFileName] 解压后的目标文件名（不含扩展名）
  /// [onProgress] 下载进度回调 (0.0 - 1.0)
  /// 返回解压后的文件路径，如果不需要下载则返回现有文件路径
  Future<String?> downloadAndExtract({
    required String url,
    required String expectedMd5,
    required String targetFileName,
    Function(double)? onProgress,
  }) async {
    try {
      final geoDir = await _getGeoDirectory();
      final targetFilePath = path.join(geoDir.path, targetFileName);
      final zipFilePath = path.join(geoDir.path, '$targetFileName.zip');
      final zipFile = File(zipFilePath);

      // 检查 zip 文件是否存在且 MD5 匹配
      bool needDownload = true;
      if (await zipFile.exists()) {
        final zipMd5 = await _calculateFileMd5(zipFilePath);
        log(
          TAG,
          '$targetFileName.zip local MD5: $zipMd5, expected: $expectedMd5',
        );

        if (zipMd5 != null &&
            zipMd5.toLowerCase() == expectedMd5.toLowerCase()) {
          log(
            TAG,
            '$targetFileName.zip already exists with correct MD5, skip download',
          );
          needDownload = false;
          onProgress?.call(0.7); // 跳过下载，直接到解压阶段
        } else {
          log(TAG, '$targetFileName.zip MD5 mismatch, will download again');
          // MD5 不匹配，删除旧的 zip 文件
          await zipFile.delete();
        }
      }

      // 下载 zip 文件（如果需要）
      if (needDownload) {
        log(TAG, 'Downloading $targetFileName from $url');

        try {
          final response = await _dio.download(
            url,
            zipFilePath,
            onReceiveProgress: (received, total) {
              if (total != -1 && onProgress != null) {
                // 下载进度占 70%
                onProgress(received / total * 0.7);
              }
            },
          );

          log(TAG, 'Download response status: ${response.statusCode}');

          if (response.statusCode != 200) {
            log(
              TAG,
              'Error: Download failed with status code ${response.statusCode}',
            );
            return null;
          }
        } catch (e) {
          log(TAG, 'Error during download: $e');
          // 清理可能产生的不完整文件
          if (await zipFile.exists()) {
            await zipFile.delete();
          }
          return null;
        }

        log(TAG, 'Downloaded to $zipFilePath');

        // 验证下载的文件是否存在
        if (!await zipFile.exists()) {
          log(TAG, 'Error: Downloaded zip file does not exist');
          return null;
        }

        // 检查文件大小
        final fileSize = await zipFile.length();
        log(TAG, 'Downloaded zip file size: $fileSize bytes');
        if (fileSize == 0) {
          log(TAG, 'Error: Downloaded zip file is empty');
          await zipFile.delete();
          return null;
        }

        onProgress?.call(0.7);
      }

      // 读取 zip 文件
      final zipBytes = await zipFile.readAsBytes();

      // 解压
      log(TAG, 'Extracting $targetFileName...');
      final archive = ZipDecoder().decodeBytes(zipBytes);

      bool extracted = false;
      // 查找目标文件（去掉 .zip 后缀的文件名）
      for (final file in archive) {
        if (file.isFile) {
          final fileName = file.name;
          log(TAG, 'Found file in archive: $fileName');

          // 忽略 macOS 元数据文件和隐藏文件
          if (fileName.startsWith('__MACOSX/') ||
              fileName.startsWith('.') ||
              path.basename(fileName).startsWith('._')) {
            log(TAG, 'Skipping system file: $fileName');
            continue;
          }

          // 只提取 .dat 文件到根目录
          if (fileName.endsWith('.dat')) {
            // 提取文件内容
            final fileData = file.content as List<int>;
            // 使用 basename 确保文件保存在根目录
            final extractedFile = File(
              path.join(geoDir.path, path.basename(fileName)),
            );
            await extractedFile.writeAsBytes(fileData);
            log(TAG, 'Extracted to ${extractedFile.path}');
            extracted = true;
          }
        }
      }

      if (!extracted) {
        log(TAG, 'Warning: No .dat file found in archive');
      }

      onProgress?.call(0.9);

      // 保留 zip 文件（不删除，下次启动可以通过 MD5 判断是否需要重新下载）
      log(TAG, 'Keeping zip file for future MD5 validation');

      // 验证解压后的文件是否存在
      final targetFile = File(targetFilePath);
      if (!await targetFile.exists()) {
        log(TAG, 'Error: Target file does not exist after extraction');
        onProgress?.call(1.0);
        return null;
      }

      // 记录成功信息
      final fileSize = await targetFile.length();
      log(TAG, '$targetFileName extracted successfully, size: $fileSize bytes');
      log(TAG, 'Zip file MD5: $expectedMd5 (validated)');

      onProgress?.call(1.0);
      return targetFilePath;
    } catch (e) {
      log(TAG, 'Error downloading/extracting $targetFileName: $e');

      // 注意：不删除 zip 文件，因为：
      // 1. 如果 zip 已经 MD5 验证通过，不应该删除
      // 2. 如果下载失败，在下载环节已经清理过了
      // 3. 保留 zip 文件便于调试和下次快速恢复

      return null;
    }
  }

  /// 下载 SmartGeo 数据（geosite 和 geoip）
  /// [smartGeo] SmartGeo 配置数据
  /// [onGeoSiteProgress] geosite 下载进度回调
  /// [onGeoIpProgress] geoip 下载进度回调
  /// 返回 Map，包含 geosite 和 geoip 的文件路径
  Future<Map<String, String?>> downloadSmartGeo({
    required SmartGeo smartGeo,
    Function(double)? onGeoSiteProgress,
    Function(double)? onGeoIpProgress,
  }) async {
    final result = <String, String?>{};

    // 下载 geosite.dat
    if (smartGeo.geoSiteUrl != null && smartGeo.geoSiteMd5 != null) {
      log(TAG, 'Downloading geosite.dat...');
      final geoSitePath = await downloadAndExtract(
        url: smartGeo.geoSiteUrl!,
        expectedMd5: smartGeo.geoSiteMd5!,
        targetFileName: 'geosite.dat',
        onProgress: onGeoSiteProgress,
      );
      result['geosite'] = geoSitePath;
    }

    // 下载 geoip.dat
    if (smartGeo.geoIpUrl != null && smartGeo.geoIpMd5 != null) {
      log(TAG, 'Downloading geoip.dat...');
      final geoIpPath = await downloadAndExtract(
        url: smartGeo.geoIpUrl!,
        expectedMd5: smartGeo.geoIpMd5!,
        targetFileName: 'geoip.dat',
        onProgress: onGeoIpProgress,
      );
      result['geoip'] = geoIpPath;
    }

    return result;
  }

  /// 检查 SmartGeo 文件是否需要更新
  /// [smartGeo] SmartGeo 配置数据
  /// 返回 Map，key 为文件类型（geosite/geoip），value 为是否需要更新
  /// 注意：通过检查 zip 文件的 MD5 来判断是否需要更新
  Future<Map<String, bool>> checkNeedUpdate(SmartGeo smartGeo) async {
    final result = <String, bool>{};

    try {
      final geoDir = await _getGeoDirectory();

      // 检查 geosite.dat.zip
      if (smartGeo.geoSiteUrl != null && smartGeo.geoSiteMd5 != null) {
        final geoSiteZipFile = File(path.join(geoDir.path, 'geosite.dat.zip'));
        if (await geoSiteZipFile.exists()) {
          final localMd5 = await _calculateFileMd5(geoSiteZipFile.path);
          // 如果 MD5 不同，则需要更新
          result['geosite'] =
              localMd5?.toLowerCase() != smartGeo.geoSiteMd5?.toLowerCase();
        } else {
          // zip 文件不存在，需要下载
          result['geosite'] = true;
        }
      }

      // 检查 geoip.dat.zip
      if (smartGeo.geoIpUrl != null && smartGeo.geoIpMd5 != null) {
        final geoIpZipFile = File(path.join(geoDir.path, 'geoip.dat.zip'));
        if (await geoIpZipFile.exists()) {
          final localMd5 = await _calculateFileMd5(geoIpZipFile.path);
          // 如果 MD5 不同，则需要更新
          result['geoip'] =
              localMd5?.toLowerCase() != smartGeo.geoIpMd5?.toLowerCase();
        } else {
          // zip 文件不存在，需要下载
          result['geoip'] = true;
        }
      }
    } catch (e) {
      log(TAG, 'Error checking update: $e');
    }

    return result;
  }

  /// 获取 geo 文件路径
  /// [fileName] 文件名（如 'geosite.dat' 或 'geoip.dat'）
  Future<String> getGeoFilePath(String fileName) async {
    final geoDir = await _getGeoDirectory();
    return path.join(geoDir.path, fileName);
  }

  /// 清除所有 geo 文件
  Future<void> clearGeoFiles() async {
    try {
      final geoDir = await _getGeoDirectory();
      if (await geoDir.exists()) {
        await geoDir.delete(recursive: true);
        await geoDir.create(recursive: true);
        log(TAG, 'Cleared all geo files');
      }
    } catch (e) {
      log(TAG, 'Error clearing geo files: $e');
    }
  }

  /// 诊断工具：列出 geo 目录下的所有文件
  Future<Map<String, dynamic>> diagnose() async {
    try {
      final geoDir = await _getGeoDirectory();
      final result = <String, dynamic>{
        'geoDirectory': geoDir.path,
        'directoryExists': await geoDir.exists(),
        'files': <Map<String, dynamic>>[],
      };

      if (await geoDir.exists()) {
        await for (final entity in geoDir.list()) {
          if (entity is File) {
            final file = entity;
            final fileSize = await file.length();
            final fileMd5 = await _calculateFileMd5(file.path);
            result['files'].add({
              'name': path.basename(file.path),
              'path': file.path,
              'size': fileSize,
              'md5': fileMd5,
            });
          }
        }
      }

      log(TAG, 'Diagnosis result: $result');
      return result;
    } catch (e) {
      log(TAG, 'Error during diagnosis: $e');
      return {'error': e.toString()};
    }
  }
}
