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

import 'package:flutter/widgets.dart';
import 'package:mnemo_cards_common/mnemo_cards_common.dart';

import 'package:path_provider/path_provider.dart';
import 'package:shared_preferences/shared_preferences.dart';

import '../../di/locator.dart';
import '../analytics/analytics.dart';

// persist
class PackCacheManager {
  late final SharedPreferences _sharedPreferences;

  PackCacheManager();

  Future<void> init() async {
    _sharedPreferences = await SharedPreferences.getInstance();
  }

  Future<void> markPackForUpdate(String packId) async {
    try {
      final dir = await _packDirectory(packId);
      final file = File('${dir.path}/pack.json');
      if (file.existsSync()) {
        final json = (await file.readAsString()).decode<Map>();
        json['id'] = '0';
        await file.writeAsString(jsonEncode(json));
      }
    } catch (e, s) {
      log('Cant marck pack for update', error: e, stackTrace: s);
      Analytics.crashlyticsError(
        e: e,
        s: s,
        message: 'Cant mark pack for update',
      );
    }
  }

  Future<CardPackDto> loadPackFromCache(
    String packId, {
    bool withCardImages = true,
  }) async {
    log('Loading from cache ${packId}');
    try {
      final packDir = await _packDirectory(packId);
      final packFile = File('${packDir.path}/pack.json');
      if (!packFile.existsSync()) {
        log('Pack file not exist');
        return throw Exception('Pack file not exist');
      }
      var pack = packFile.readAsStringSync().decode(CardPackDto.fromJson);
      if (withCardImages) {
        Map<String, MemoryImage> images = {};
        log('Cards ${packId}: ${pack.cards.length}');
        if (pack.cards.isNotEmpty) {
          log('Loading cards ${packId}');
          await Future.wait(
            pack.cards.map((dto) async {
              final file = File('${packDir.path}/images/${dto.id}');
              if (await file.exists()) {
                try {
                  images[dto.id.toString()] =
                      MemoryImage(await file.readAsBytes());
                } catch (e) {
                  markPackForUpdate(packId);
                  throw Exception('Cant read file $packId $e');
                }
              } else {
                log('Cant load card ${dto.id} from $packId, skiping');
              }
            }),
          );
        }
        log('Pack loaded ${packId}');
        locator.imagesHolder.setPackImages(images, packId);
      }
      return pack;
    } catch (e, s) {
      log('Cant load from cache ${packId} ${e} ${s}');
      deletePack(packId);
      rethrow;
    }
  }

  Future<List<String>> _updateSavedPacks() async {
    final packsDir =
        Directory('${(await getApplicationDocumentsDirectory()).path}/packs');
    List<String> savedPackIds = [];
    if (packsDir.existsSync()) {
      for (final packDir in packsDir.listSync()) {
        if (File('${packDir.path}/pack.json').existsSync()) {
          savedPackIds.add(packDir.path.split('/').last);
        }
      }
    }
    _sharedPreferences.setStringList('packs', savedPackIds);
    return savedPackIds;
  }

  Future<List<CardPackDto>> loadAllPacks() async {
    final ids = await _updateSavedPacks();
    final List<CardPackDto> packs = [];
    for (final id in ids) {
      try {
        final pack = await loadPackFromCache(id, withCardImages: false);
        packs.add(pack);
      } catch (e) {
        log('Pack $id doesnt exist anymore');
      }
    }
    return packs;
  }

  Future<void> deletePack(String packId) async {
    try {
      final dir = await _packDirectory(packId);
      dir.deleteSync(recursive: true);
    } catch (e) {
      log('Deletion failed $e');
    }
    _updateSavedPacks();
  }

  Future<Uint8List?> loadPackFile(String packId, String path) async {
    final bytes =
        File('${(await _packDirectory(packId)).path}/$path').readAsBytesSync();
    return bytes;
  }

  Future<void> clearCache() async {
    final saved = await _updateSavedPacks();
    for (final p in saved) {
      await deletePack(p);
    }
    await _sharedPreferences.remove('packs');
  }

  Future<void> savePack(
    CardPackDto dto, {
    Map<String, MemoryImage>? images,
  }) async {
    log('Saving pack ${dto.id}');
    final dir = await _packDirectory(dto.id);
    final json = dto.encode();
    log('Saving dto ${dto.id}');
    File('${dir.path}/pack.json')
      ..createSync(recursive: true)
      ..writeAsStringSync(json);

    if (images != null) {
      log('Saving card images ${dto.id}');
      final imageDir = Directory('${dir.path}/images');
      if (imageDir.existsSync()) {
        imageDir.deleteSync(recursive: true);
      }
      for (final card in dto.cards) {
        final bytes = images[card.id.toString()]?.bytes;
        if (bytes != null) {
          File('${imageDir.path}/${card.id}')
            ..createSync(recursive: true)
            ..writeAsBytesSync(bytes);
        }
      }
    }

    log('Saved ${dto.id}');
  }

  Future<Directory> _packDirectory(String id) async => Directory(
        '${(await getApplicationDocumentsDirectory()).path}/packs/$id',
      );
}
