import 'dart:async' as async;
import 'dart:async';
import 'dart:convert';

import 'package:fis_common/index.dart';
import 'package:fis_common/logger/logger.dart';
import 'package:fis_i18n/i18n.dart';
import 'package:fis_jsonrpc/rpc.dart';
import 'package:flutter/foundation.dart';
import 'package:flyinsono/architecture/states/states.dart';
import 'package:flyinsono/architecture/utils/prompt_box.dart';
import 'package:flyinsono/rpc.dart';
import 'package:http/http.dart' as http;
import 'package:universal_html/html.dart' as html;
import 'package:xml/xml.dart';

const int chunkPartFileSize = 200 * 1024 * 1024; // 每个分割的文件的大小，此处设置为 200MB
const int chunkSize = 5 * 1024 * 1024; // 每个分块的大小，此处设置为5MB

const Map<String, String> contentTypes = {
  'jpg': 'image/jpeg',
  'jpeg': 'image/jpeg',
  'png': 'image/png',
  'gif': 'image/gif',
  'doc': 'application/msword',
  'docx':
      'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
  'xls': 'application/vnd.ms-excel',
  'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
  'ppt': 'application/vnd.ms-powerpoint',
  'pptx':
      'application/vnd.openxmlformats-officedocument.presentationml.presentation',
  'pdf': 'application/pdf',
  'txt': 'text/plain',
  'mp4': 'video/mp4',
  'avi': 'video/x-msvideo',
  'flv': 'video/x-flv',
  'mov': 'video/quicktime',
  'wmv': 'video/x-ms-wmv',
  'mpeg': 'video/mpeg',
  'mp3': 'audio/mpeg',
  'wma': 'audio/x-ms-wma',
  'zip': "application/zip",
};

class PartInfo {
  final int partNumber;
  final String eTag;
  final String uploadId;

  PartInfo(
      {required this.partNumber, required this.eTag, required this.uploadId});
}

class ProgressByteStream extends http.ByteStream {
  final async.Stream<List<int>> _source;
  final void Function(double)? onProgress;
  final int totalBytes;

  factory ProgressByteStream({
    required async.Stream<List<int>> source,
    void Function(double)? onProgress,
    required int totalBytes,
  }) {
    async.Stream<List<int>> progressStream =
        progressTransformer(source, onProgress, totalBytes);
    return ProgressByteStream._internal(
      source: progressStream,
      onProgress: onProgress,
      totalBytes: totalBytes,
      stream: progressStream,
    );
  }

  ProgressByteStream._internal({
    required async.Stream<List<int>> source,
    this.onProgress,
    required this.totalBytes,
    required async.Stream<List<int>> stream,
  })  : _source = source,
        super(stream);

  static async.Stream<List<int>> progressTransformer(
      async.Stream<List<int>> source,
      void Function(double)? onProgress,
      int totalBytes) {
    int bytesSent = 0;
    async.StreamController<List<int>> controller =
        async.StreamController<List<int>>();

    source.listen(
      (List<int> data) {
        bytesSent += data.length;
        if (onProgress != null) {
          double progress = bytesSent / totalBytes;
          onProgress(progress);
        }
        controller.add(data);
      },
      onError: controller.addError,
      onDone: () => controller.close(),
      cancelOnError: false,
    );

    return controller.stream;
  }
}

class UploadDeviceFileInfo {
  int? fileSize;

  List<UploadFileInfo>? fileInfos;

  String? fileName;

  String? token;

  UploadDeviceFileInfo(
      this.fileName, this.token, this.fileInfos, this.fileSize);
}

class UploadFileAuthInfo {
  String uploadId;

  StorageServiceSettingDTO auth;

  int filePartNum;

  String partFileName;

  UploadFileAuthInfo(
      this.uploadId, this.auth, this.filePartNum, this.partFileName);
}

class UploadFileInfo {
  int partNum;

  String fileToken;

  UploadFileInfo(this.partNum, this.fileToken);
}

extension StorageServiceExt on StorageService {
  Future<String?> completeMultipartUpload(
      String fileName, String uploadId, List<PartInfo> uploadedParts) async {
    uploadedParts.sort(((a, b) => a.partNumber.compareTo(b.partNumber)));
    if (kDebugMode) {
      print(
          '文件合并：$fileName,uploadedParts length:${uploadedParts.length},uploadId:$uploadId');
    }

    final auth = await getAuth(
      fileName: fileName,
      isRechristen: false,
      headerParams: [
        DataItemDTO(key: "Content-Type", value: "application/xml")
      ],
      urlParams: [
        DataItemDTO(
          key: 'uploadId',
          value: uploadId,
        ),
      ],
      requestMethod: 'post',
    );
    final String url = "${auth.storageUrl}?uploadId=$uploadId";

    // 构建要发送给服务器的 XML 数据
    String xmlBody = '<CompleteMultipartUpload>';
    for (int i = 1; i <= uploadedParts.length; i++) {
      xmlBody += '<Part>';
      xmlBody += '<PartNumber>$i</PartNumber>';
      xmlBody += '<ETag>${uploadedParts[i - 1].eTag}</ETag>';
      xmlBody += '</Part>';
    }
    xmlBody += '</CompleteMultipartUpload>';

    try {
      final client = http.Client();
      final request = http.Request('POST', Uri.parse(url));
      request.headers['Authorization'] = auth.authorization!;
      request.headers['Content-Type'] = 'application/xml';
      request.bodyBytes = utf8.encode(xmlBody); // 手动将 XML 字符串转换为字节数组
      final response = await client.send(request);

      // 根据需要处理响应
      if (response.statusCode == 200) {
        return auth.storageUrl;
      } else {
        throw Exception('Failed to complete multipart upload');
      }
    } catch (e) {
      print(e);
      throw e;
    }
  }

  async.Stream<List<int>> createProgressStream(
    async.Stream<List<int>> source,
    int totalBytes, {
    void Function(double)? onProgress,
  }) {
    int bytesSent = 0;

    final progressTransformer =
        async.StreamTransformer<List<int>, List<int>>.fromHandlers(
      handleData: (List<int> data, async.EventSink<List<int>> sink) {
        bytesSent += data.length;
        if (onProgress != null) {
          double progress = bytesSent / totalBytes;
          onProgress(progress);
        }
        sink.add(data);
      },
    );

    return source.transform(progressTransformer);
  }

  Future<StorageServiceSettingDTO> getAuth({
    String? fileName,
    bool? isRechristen,
    List<DataItemDTO>? urlParams,
    List<DataItemDTO>? headerParams,
    String? requestMethod,
  }) async {
    try {
      final result = await rpc.storage.getAuthorizationAsync(FileServiceRequest(
        token: Store.user.token,
        fileName: fileName ?? "dat",
        isRechristen: isRechristen ?? true,
        urlParams: urlParams,
        headerParams: headerParams,
        requestMethod: requestMethod,
      ));
      return result;
    } catch (e) {
      return StorageServiceSettingDTO();
    }
  }

  Future<String> getVodAuthorizationAsync() async {
    try {
      final result = await rpc.authentication.getVodAuthorizationAsync(
        TokenRequest(
          token: Store.user.token,
        ),
      );
      return result;
    } catch (e) {
      return '';
    }
  }

  Future<String> initiateMultipartUpload(String fileName) async {
    final auth = await getAuth(
      fileName: fileName,
      requestMethod: 'post',
      isRechristen: false,
    );
    var authUrl = auth.storageUrl ?? '';
    final String url = '$authUrl?uploads';
    final Map<String, String> headers = {
      "Content-Type": getContentType(fileName),
      "Authorization": auth.authorization ?? '',
    };

    try {
      final response = await http.post(
        Uri.parse(url),
        headers: headers,
      );
      if (response.statusCode == 200) {
        // 解析 response 中的 uploadId
        final String responseXml = response.body;
        final XmlDocument xmlDoc = XmlDocument.parse(responseXml);
        final XmlElement? uploadIdElement =
            xmlDoc.findAllElements('UploadId').first;

        if (uploadIdElement != null) {
          final String uploadId = uploadIdElement.text;
          return uploadId;
        } else {
          throw Exception('Failed to find uploadId');
        }
      } else {
        throw Exception('Failed to initiate multipart upload');
      }
    } catch (e) {
      print(e);
      throw e;
    }
  }

  ///选择一个zip文件并上传
  Future<html.File?> pickAndUploadFile({
    void Function(double)? onProcessChange,
    void Function(String)? onSelectedFile,
  }) async {
    final fileInput = html.FileUploadInputElement()
      ..accept =
          'application/zip,application/x-zip-compressed,application/x-compressed-zip,application/vnd.android.package-archive,.zip,.rar,.tar,.tar.gz,.apk'; // 设置支持的文件类型
    fileInput.click(); // 打开文件选择器
    await fileInput.onChange.first;
    final file = fileInput.files!.first;
    final allowedFileTypes = [
      'application/zip',
      'application/x-zip-compressed',
      'application/x-compressed-zip',
      'application/x-rar-compressed',
      'application/x-tar',
      'application/x-gzip',
      'application/x-apk',
      'application/apk',
      'application/vnd.android.package-archive'
    ];

    if (!allowedFileTypes.contains(file.type)) {
      // 如果文件类型不符合要求，则提示用户重新选择文件
      PromptBox.toast(i18nBook.common.pleaseSelectZipFile.t);
      return null;
    }
    final fileName = file.name;
    onSelectedFile?.call(fileName);

    return file;
  }

  Future<UploadDeviceFileInfo?> readAndUploadFile(
      html.File file, void Function(double)? onProcessChange,
      [bool Function()? checkCancel]) async {
    checkCancel ??= () => false;
    if (checkCancel.call()) {
      return null;
    }
    final fileSize = file.size;
    final fileName = file.name;
    double _processValue = 0;
    // 计算所需的分片数量
    final totalChunksCount = (fileSize / chunkSize).ceil();
    //单个文件所需分片数量
    final partFileTotalCount =
        ((kDebugMode ? 50 * 1024 * 1024 : chunkPartFileSize) / chunkSize)
            .ceil();
    //文件分割的数量
    final partFileNum = (totalChunksCount / partFileTotalCount).ceil();
    List<UploadFileInfo> fileInfos = [];
    List<PartInfo> uploadedPartInfos = [];
    int filePartNum = -1;
    String uploadId = '';
    StorageServiceSettingDTO auth = StorageServiceSettingDTO();

    List<UploadFileAuthInfo> uploadFileInfos = [];
    for (int i = 0; i < totalChunksCount; ++i) {
      if (checkCancel.call()) {
        return null;
      }
      int currentFilePartNum = ((i + 1) / partFileTotalCount).ceil() - 1;
      String partFileName = '$fileName.part$currentFilePartNum';

      if (kDebugMode) {
        print('partFileName:$partFileName,current index:$i');
      }
      if (currentFilePartNum != filePartNum) {
        filePartNum = currentFilePartNum;
        // 初始化分块上传
        if (checkCancel.call()) {
          return null;
        }
        uploadId = await initiateMultipartUpload(partFileName);
        if (checkCancel.call()) {
          return null;
        }
        auth = await getAuth(
          fileName: partFileName,
          isRechristen: false,
          urlParams: [
            DataItemDTO(
              key: 'uploadId',
              value: uploadId,
            ),
          ],
        );
        uploadFileInfos
            .add(UploadFileAuthInfo(uploadId, auth, filePartNum, partFileName));
        if (i % 10 == 0) {
          _processValue += ((i + 1) / totalChunksCount) * 0.03;
        }
      }
    }

    int taskCount = 0;
    List<Future<PartInfo?>> tasks = []; //上传任务列表
    Map<int, double> processValues = {};

    for (var i = 0; i < totalChunksCount; i++) {
      final completer = Completer<PartInfo?>();
      if (checkCancel.call()) {
        return null;
      }
      Future<PartInfo?> uploadItem() async {
        int currentFilePartNum = ((i + 1) / partFileTotalCount).ceil() - 1;
        String partFileName = '$fileName.part$currentFilePartNum';
        var uploadinfo = uploadFileInfos
            .firstWhere((element) => element.filePartNum == currentFilePartNum);
        var partInfo = await uploadItemPart(
          i,
          totalChunksCount,
          fileSize,
          file,
          auth: uploadinfo.auth,
          partFileTotalCount: partFileTotalCount,
          uploadId: uploadinfo.uploadId,
          partFileName: partFileName,
          checkCancel: checkCancel,
          onProcessChange: (v) {
            processValues[i] = v;
            double sum = processValues.values.reduce((a, b) => a + b);
            _processValue = 0.03 + (sum * 0.96) / totalChunksCount;
            if (kDebugMode) {
              logger.i('进度更新：$_processValue');
            }
            if (_processValue < 0.99) {
              onProcessChange?.call(_processValue);
            }
          },
        );
        return partInfo;
      }

      var task = uploadItem();
      task.then((partInfo) async {
        taskCount--;
        completer.complete(partInfo);

        if (partInfo != null) {
          if (kDebugMode) {
            print(
                '分块上传完成 partInfo: partNumber：${partInfo.partNumber} eTag：${partInfo.eTag} uploadId：${partInfo.uploadId}');
          }
          uploadedPartInfos.add(partInfo);
        }
      });
      tasks.add(completer.future);
      taskCount++;
      // 控制上传并发数
      while (taskCount >= 5) {
        await Future.wait(tasks);
      }

      if (i == totalChunksCount - 1) {
        await Future.wait(tasks);
      }
    }
    logger.i('task length:${tasks.length} taskCount:$taskCount');

    ///文件合并，大文件按照chunkPartFileSize大小分割成小文件
    for (var file in uploadFileInfos) {
      var filePartNum = file.filePartNum;
      var partInfos = uploadedPartInfos
          .where((element) => element.uploadId == file.uploadId)
          .toList();
      if (kDebugMode) {
        for (var j in partInfos) {
          print(
              '将要合并的分块信息：PartInfo： partNumber:${j.partNumber} eTag:${j.eTag} uploadId:${j.uploadId}');
        }
      }
      String partFileName = '$fileName.part$filePartNum';
      //当前单个分割文件，合并文件
      var url =
          await completeMultipartUpload(partFileName, file.uploadId, partInfos);
      fileInfos.add(UploadFileInfo(file.filePartNum, url!));
    }

    if (kDebugMode) {
      for (var f in fileInfos) {
        print(f.fileToken);
      }
    }
    onProcessChange?.call(1);
    return UploadDeviceFileInfo(fileName, '', fileInfos, fileSize);
  }

  // 定义一个异步函数发送请求,模拟进度更新，使进度更新看起来更平滑
  Future<http.Response> sendRequest(
      String url, Map<String, String> headers, Uint8List chunk,
      {void Function(double)? onProgress}) async {
    bool requestCompleted = false;
    double currentProgress = 0.2;

    // 创建一个定时器，在请求期间每500毫秒更新进度
    async.Timer.periodic(Duration(milliseconds: 500), (timer) {
      if (!requestCompleted) {
        if (currentProgress < 0.8) {
          currentProgress += 0.07; // 假设每次更新增加7%，可以根据实际需求调整
          onProgress?.call(currentProgress);
        }
      } else {
        // 请求发送完成，取消定时器
        currentProgress = 1.0;
        onProgress?.call(1.0);
        timer.cancel();
      }
    });
    // 发送请求
    final response = await http.put(
      Uri.parse(url),
      headers: headers,
      body: chunk,
    );
    if (kDebugMode) {
      logger.i('请求结束$currentProgress');
    }
    requestCompleted = true;
    return response;
  }

  Future<PartInfo> uploadPart(
    String fileName,
    String uploadId,
    int partNumber,
    Uint8List chunk, {
    required StorageServiceSettingDTO auth,
    required int numChunks,
    required int currentIndex,
    void Function(double)? onProcessChange,
  }) async {
    var authUrl = auth.storageUrl ?? '';
    if (auth.authorization.isNullOrEmpty) {
      logger.e('uploadPart error: auth is empty');
    }
    final String url = '$authUrl?partNumber=$partNumber&uploadId=$uploadId';
    try {
      async.Stream<List<int>> inputStream = async.Stream.fromIterable([chunk]);
      ProgressByteStream progressStream = ProgressByteStream(
        source: inputStream,
        totalBytes: chunk.length,
      );
      http.MultipartRequest request = http.MultipartRequest(
        "PUT",
        Uri.parse(url),
      );
      // 添加 'Authorization' 头到请求
      request.headers['Authorization'] = auth.authorization ?? '';
      request.headers['Content-Type'] = "application/zip";
      request.files.add(
        http.MultipartFile(
          'file',
          progressStream,
          chunk.length,
          filename: fileName,
        ),
      );
      final Map<String, String> headers = {
        "Content-Type": "application/octet-stream",
        "Authorization": auth.authorization ?? '',
      };
      var response = await sendRequest(
        url,
        headers,
        chunk,
        onProgress: (v) {
          onProcessChange?.call(v);
        },
      );

      if (response.statusCode == 200) {
        var headers = response.headers;
        return PartInfo(
          partNumber: partNumber,
          eTag: headers['etag']?.toString() ?? '',
          uploadId: uploadId,
        );
      } else {
        throw Exception('Failed to upload part $partNumber');
      }
    } catch (e) {
      print(e);
      throw e;
    }
  }

  Future<PartInfo?> uploadItemPart(
      int i, int totalChunksCount, fileSize, html.File file,
      {required String partFileName,
      required String uploadId,
      required int partFileTotalCount,
      required StorageServiceSettingDTO auth,
      void Function(double)? onProcessChange,
      bool Function()? checkCancel}) async {
    checkCancel ??= () => false;
    bool readEnd = false;
    double _processValue = 0;
    async.Timer.periodic(Duration(microseconds: 300), (timer) {
      if (checkCancel!.call()) {
        return;
      }
      if (!readEnd) {
        if (_processValue < 0.2) {
          _processValue += 0.02;
          onProcessChange?.call(_processValue);
        }
      } else {
        timer.cancel();
        _processValue = 0.2;
        onProcessChange?.call(_processValue);
      }
    });
    final chunkStart = i * chunkSize;
    final chunkEnd =
        (i == totalChunksCount - 1) ? fileSize : chunkStart + chunkSize;
    final blob = file.slice(chunkStart, chunkEnd);
    final reader = html.FileReader();
    reader.readAsArrayBuffer(blob);
    await reader.onLoadEnd.first;
    final data = reader.result;
    readEnd = true;
    if (data != null && data is Uint8List) {
      final Uint8List chunkData = data;
      if (checkCancel.call()) {
        return null;
      }
      final PartInfo partInfo = await uploadPart(
        partFileName,
        uploadId,
        i % partFileTotalCount + 1,
        chunkData,
        auth: auth,
        currentIndex: i + 1,
        onProcessChange: onProcessChange,
        numChunks: totalChunksCount,
      );
      return partInfo;
    }
    return null;
  }

  String getContentType(String fileName) {
    String extension = fileName.split('.').last;
    return contentTypes[extension] ?? '';
  }
}
