import 'dart:async';
import 'dart:ui' as ui;

import 'package:flutter/foundation.dart';
import 'package:http/http.dart';
import 'package:puzzleweave/config/device.dart';
import 'package:puzzleweave/models/items.dart';
import 'package:puzzleweave/utils/utils.dart';
import 'package:logging/logging.dart';

final Logger _log = Logger('download.dart');

/// 最多缓存/并发下载n个图到内存
const maxCachedItems = 1;

/// Sigeleton
class Download {
  static final Download _instance = Download._internal();

  factory Download() {
    return _instance;
  }
  Download._internal();

  final Map<String, DownloadItem> _cache = {};

  DownloadItem download(url, cachePath) {
    // 移除同步 _clean() 调用，避免干扰正在启动的下载序列
    // _clean();
    if (_cache[url] != null) {
      _log.info('Cache hit for $url');
      _cache[url]!.touch(); //update last use time.
      return _cache[url]!;
    } else {
      final item = DownloadItem(url, cachePath);
      _cache[url] = item;
      _watch(item);
      return item;
    }
  }

  _watch(DownloadItem item) async {
    try {
      await item.loadCompleter.future;
      // !!! 修正点 1: 在任务完成后，异步触发清理
      // 任务完成后，它占用的内存 Image 和 Data 就可以被清理了
      Future.microtask(_clean);
    } catch (err) {
      // 发生错误，立即移除缓存项
      _log.info('Watch download item got error: $err');
      _cache.remove(item.url);
    }
  }

  _clean() {
    // final list = _cache.values.toList();
    // 1. 筛选出可以被清理的项：必须是已完成加载（已完成下载并写入磁盘）
    final list = _cache.values
        .where((item) => item.loadCompleter.isCompleted) // !!! 修正点 2: 仅清理已完成加载的项
        .toList();
    if (list.length <= maxCachedItems) return;
    _log.info('cleaning...');

    // 2. 按最近使用时间排序（时间越早越应该被清理）
    list.sort((a, b) => a.lastUsed.compareTo(b.lastUsed));
    // 3. 清理到只剩下 maxCachedItems 个
    while (list.length > maxCachedItems) {
      final item = list.removeAt(0);
      _log.info('clean item: $item');
      item.dispose();
      _cache.remove(item.url);
    }
  }

  clearAllCached() async {
    final file = await localFile('cache');
    await file.delete(recursive: true);
  }
}

class DownloadItem {
  final String url;
  final String cachePath;
  ValueNotifier<double> progress = ValueNotifier(0.0);
  final Completer<ui.Image> loadCompleter = Completer();
  Client? client;
  StreamSubscription? subscription;
  int lastUsed;
  int size = 0;
  ui.Image? image;
  Uint8List? data;

  DownloadItem(this.url, this.cachePath) : lastUsed = DateTime.now().millisecondsSinceEpoch {
    _log.info('New download item for: $url');
    _start();
  }

  touch() {
    lastUsed = DateTime.now().millisecondsSinceEpoch;
  }

  _start() async {
    try {
      final image = await _download();
      loadCompleter.complete(image);
    } catch (err) {
      loadCompleter.completeError(err);
    }
  }

  Future<ui.Image> _download() async {
    progress.value = 0;

    final file = await localFile(cachePath);
    _checkDispose();

    final Uint8List data;
    bool shouldSave = false;

    //if (await file.exists()) {
    if (await file.exists()) {
      _log.info('Disk cache hit..');
      data = await file.readAsBytes();
      _checkDispose();
      progress.value = 1;
    } else {
      final List<int> bytes = [];

      client = Client();

      final uri = Uri.parse(url);
      final request = Request('GET', uri);

      final response = await client!.send(request);
      _checkDispose();

      if (response.statusCode != 200) {
        throw Exception('Download error, stauts:${response.statusCode}, url=$uri');
      }

      if (response.contentLength == null) {
        throw Exception('Download error, no length, url=$uri');
      }

      final length = response.contentLength!;

      _log.info('message: contentLength=$length');

      final streamCompleter = Completer();

      subscription = response.stream.listen(
        (value) {
          try {
            // 有可能内存溢出， 先try/catch包一下
            bytes.addAll(value);
            progress.value = bytes.length / length;
            _log.info('message: progress=${progress.value}');
          } catch (e) {
            _log.warning("Out of memory: $e");
            // FirebaseCrashlytics.instance.log("OOM from download url: $uri, error: $e");
            streamCompleter.completeError(e);
          }
        },
        onDone: () {
          //_log.info('onDone..');
          streamCompleter.complete();
        },
        onError: (e) {
          _log.info('onError: $e');
          streamCompleter.completeError(e);
        },
        cancelOnError: true,
      );

      await streamCompleter.future;
      //await response.stream.first;
      _log.info('xxxxxxxxxxxxxxxxxxxx stream complete');

      _checkDispose();
      _log.info('message: download succeed, progress=$progress, length=${bytes.length}');

      bytes.removeRange(0, 24);
      data = Uint8List.fromList(bytes);

      shouldSave = true;
      _checkDispose();
    }

    _log.info('message: realbytes: ${data.length}');

    int size = Device.physicalSize.width.toInt();

    final ui.Codec codec = await ui.instantiateImageCodec(data, targetHeight: size, targetWidth: size);
    final ui.FrameInfo frameInfo = await codec.getNextFrame();
    final image = frameInfo.image;
    this.image = image;

    this.data = data;
    size = data.length;

    if (shouldSave) {
      await saveBytes(cachePath, data);
    }

    _checkDispose();

    _log.info('image: ${image.width}x${image.height}');
    client?.close();

    return image;
  }

  Future<ui.Image> getImageBySize(int dim, {allowUpscaling = false}) async {
    await loadCompleter.future;
    final ui.Codec codec = await ui.instantiateImageCodec(data!, targetHeight: dim, targetWidth: dim, allowUpscaling: allowUpscaling);
    final ui.FrameInfo frameInfo = await codec.getNextFrame();
    return frameInfo.image;
  }

  bool _isDisposed = false;

  _checkDispose() {
    _log.info('$this,checkDispose: $_isDisposed');
    if (_isDisposed) throw Exception('Request disposed');
  }

  dispose() async {
    _log.info('Disposing $this, client:$client');
    _isDisposed = true;
    // do clean.
    try {
      subscription?.cancel();
      client?.close();
      image?.dispose();
    } catch (error) {
      _log.info('xxxxxxxxxxxx $error');
    }
  }

  @override
  String toString() {
    return '[$cachePath]';
  }
}

abstract class ItemLoader {
  final Completer completer = Completer();
  Uint8List? get data;
  ValueNotifier<double> get progress;

  ItemLoader();

  Future<ui.Image> getImageBySize(int width, int height, {allowUpscaling = true}) async {
    await completer.future;
    final ui.Codec codec = await ui.instantiateImageCodec(data!, targetHeight: height, targetWidth: width, allowUpscaling: allowUpscaling);
    final ui.FrameInfo frameInfo = await codec.getNextFrame();
    return frameInfo.image;
  }

  Future<ui.Image> getImage() async {
    await completer.future;
    final ui.Codec codec = await ui.instantiateImageCodec(data!);
    final ui.FrameInfo frameInfo = await codec.getNextFrame();
    return frameInfo.image;
  }

  factory ItemLoader.load(ListItem item) {
    switch (item.runtimeType) {
      case RemoteItem:
        return RemoteItemLoader((item as RemoteItem).image, item.cachePath);
      case AssetItem:
        return AssetItemLoader((item as AssetItem).image);
      default:
        throw 'Can\'t create ${item.runtimeType}';
    }
  }
}

class LocalItemLoader extends ItemLoader {
  final String path;
  Uint8List? _data;

  @override
  ValueNotifier<double> progress = ValueNotifier(0);

  LocalItemLoader(this.path) {
    _load();
  }

  _load() async {
    try {
      final file = await localFile(path);
      _data = await file.readAsBytes();
      completer.complete(data);
      progress.value = 1.0;
    } catch (error) {
      completer.completeError(error);
    }
  }

  @override
  Uint8List? get data => _data;
}

class AssetItemLoader extends ItemLoader {
  final String path;
  Uint8List? _data;

  @override
  ValueNotifier<double> progress = ValueNotifier(0);

  AssetItemLoader(this.path) {
    _load();
  }

  _load() async {
    try {
      _data = await loadFileDataFromAsset(path);
      completer.complete(data);
      progress.value = 1.0;
    } catch (error) {
      completer.completeError(error);
    }
  }

  @override
  Uint8List? get data => _data;
}

class RemoteItemLoader extends ItemLoader {
  final String url;
  final String cachePath;
  late final DownloadItem downloadItem;

  RemoteItemLoader(this.url, this.cachePath) {
    downloadItem = Download().download(url, cachePath);
    _load();
  }

  _load() async {
    try {
      await downloadItem.loadCompleter.future;
      completer.complete();
    } catch (err) {
      completer.completeError(err);
    }
  }

  @override
  Uint8List? get data => downloadItem.data;

  @override
  ValueNotifier<double> get progress => downloadItem.progress;
}
