import 'dart:async';
import 'dart:developer';
import 'dart:typed_data';

import 'package:archive/archive.dart';
import 'package:flutter/cupertino.dart';
import 'package:mnemo_cards/features/packs/packs_api.dart';
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
import 'package:rxdart/rxdart.dart';
import '../../features/packs/pack_cache_manager.dart';
import '../../features/packs/pack_holder.dart';
import 'preview_pack_holder.dart';

/// Updates cache and state holders for available packs
/// Does polling
class PackUpdater {
  final PacksApi _packsApi;
  final PackCacheManager _cacheManager;
  final PackHolder _packHolder;
  final PreviewPackHolder _previewPackHolder;

  Completer? _updateCompleter;

  PackUpdater(
    this._packsApi,
    this._cacheManager,
    this._packHolder,
    this._previewPackHolder,
  );

  Future<void> init() async {
    final pollStream = Stream.periodic(const Duration(seconds: 60));
    pollStream.listen((event) async {
      await updateAvailablePacks();
      try {
        final r = await _packsApi.getPacksPreviews(null);
        _previewPackHolder.setPreviewPacks(r, save: true);
      } catch (e) {
        log(e.toString());
      }
    });
  }

  Future<void> updateAvailablePacks() async {
    if (_updateCompleter?.isCompleted ?? true) {
      _updateCompleter = Completer();
    }
    unawaited(_updateAvailablePacks().whenComplete(() {
      if (_updateCompleter?.isCompleted == false) {
        _updateCompleter?.complete();
      }
    }));
    return _updateCompleter!.future;
  }

  Future<void> _updateAvailablePacks() async {
    final savedList = await _cacheManager.loadAllPacks();
    final packData = Map.fromEntries(
      savedList.map(
        (e) => MapEntry(
          e.id.toString(),
          e.version,
        ),
      ),
    );
    final actions = await _packsApi.getPacksActions(packData);
    Map<String, CardPackDto> updatedPacks = {};
    bool shouldUpdateHolder = false;

    for (final action in actions) {
      switch (action.action) {
        case PackAction.delete:
          log('DELETE ${action.id}');
          shouldUpdateHolder = true;
          await _cacheManager.deletePack(action.id);
          savedList.removeWhere((p) => p.id == action.id);
          break;
        case PackAction.update:
          log('UPDATE ${action.id}');
          shouldUpdateHolder = true;
          try {
            final updatedPack = await updatePackAndSaveImages(action.id);
            updatedPacks[action.id] = updatedPack;
          } catch (e) {
            log('Error while download cards for ${action.id} $e');
          }
          break;
        case PackAction.unknown:
          log('Unknown action on update ${action.id}');
          break;
      }
    }
    if (shouldUpdateHolder) {
      List<CardPackDto> packs = [];
      for (final saved in savedList) {
        if (updatedPacks[saved.id] != null) {
          packs.add(updatedPacks[saved.id]!);
          updatedPacks.remove(saved.id);
        } else {
          packs.add(saved);
        }
      }
      packs.addAll(updatedPacks.values);
      _packHolder.setPacks(packs);
    }
  }

  Future<CardPackDto> updatePackAndSaveImages(String packId) async {
    try {
      log('Updating with cards ${packId}', name: 'updatePackWithCards');
      final packDto = await _packsApi.getPackDto(packId);
      if (packDto == null) {
        throw Exception('Cant update pack $packId');
      }
      final imagesData = await _packsApi.packImagesArchive(packId);
      Archive imagesArchive = ZipDecoder().decodeBytes(imagesData);
      Map<String, MemoryImage> images = {};
      for (final file in imagesArchive) {
        final fileData = file.content as List<int>;
        images[file.name] = MemoryImage(
          Uint8List.fromList(fileData),
        );
      }
      _cacheManager.savePack(packDto, images: images);
      log('Pack ${packId} updated with cards');
      return packDto;
    } catch (e, s) {
      log('Cant update pack', name: 'updatePackWithCards');
      log(e.toString(), stackTrace: s);
      rethrow;
    }
  }
}
