import 'dart:async';
import 'dart:io';

import '../../../shared/utils/async_lock.dart';
import '../../contracts/library.dart';
import 'json_file_cache_io.dart';

class HomeOverviewCacheData {
  final List<EmbyRawLibrary> libraries;
  final List<EmbyRawItem> resumeItems;
  final Map<String, List<EmbyRawItem>> latestByLibrary;
  final DateTime savedAt;

  const HomeOverviewCacheData({
    required this.libraries,
    required this.resumeItems,
    required this.latestByLibrary,
    required this.savedAt,
  });

  factory HomeOverviewCacheData.empty() => HomeOverviewCacheData(
    libraries: const [],
    resumeItems: const [],
    latestByLibrary: const {},
    savedAt: DateTime.fromMillisecondsSinceEpoch(0),
  );

  factory HomeOverviewCacheData.fromJson(Map<String, dynamic> json) {
    final libs = (json['libraries'] as List<dynamic>? ?? [])
        .map((e) => EmbyRawLibrary.fromJson(e as Map<String, dynamic>))
        .toList();
    final resume = (json['resumeItems'] as List<dynamic>? ?? [])
        .map((e) => EmbyRawItem.fromJson(e as Map<String, dynamic>))
        .toList();
    final latestRaw = json['latestByLibrary'] as Map<String, dynamic>? ?? {};
    final latest = latestRaw.map(
      (k, v) => MapEntry(
        k,
        (v as List<dynamic>)
            .map((e) => EmbyRawItem.fromJson(e as Map<String, dynamic>))
            .toList(),
      ),
    );
    return HomeOverviewCacheData(
      libraries: libs,
      resumeItems: resume,
      latestByLibrary: latest,
      savedAt: DateTime.parse(json['savedAt'] as String),
    );
  }

  Map<String, dynamic> toJson() => {
    'libraries': libraries.map((e) => e.toJson()).toList(),
    'resumeItems': resumeItems.map(_itemToCacheJson).toList(),
    'latestByLibrary': latestByLibrary.map(
      (k, v) => MapEntry(k, v.map(_itemToCacheJson).toList()),
    ),
    'savedAt': savedAt.toUtc().toIso8601String(),
  };

  HomeOverviewCacheData copyWith({
    List<EmbyRawLibrary>? libraries,
    List<EmbyRawItem>? resumeItems,
    Map<String, List<EmbyRawItem>>? latestByLibrary,
  }) => HomeOverviewCacheData(
    libraries: libraries ?? this.libraries,
    resumeItems: resumeItems ?? this.resumeItems,
    latestByLibrary: latestByLibrary ?? this.latestByLibrary,
    savedAt: DateTime.now().toUtc(),
  );
}

class HomeBannerCacheData {
  final List<EmbyRawItem> bannerItems;
  final DateTime savedAt;

  const HomeBannerCacheData({required this.bannerItems, required this.savedAt});

  factory HomeBannerCacheData.fromJson(Map<String, dynamic> json) {
    final items = (json['bannerItems'] as List<dynamic>? ?? [])
        .map((e) => EmbyRawItem.fromJson(e as Map<String, dynamic>))
        .toList();
    return HomeBannerCacheData(
      bannerItems: items,
      savedAt: DateTime.parse(json['savedAt'] as String),
    );
  }

  Map<String, dynamic> toJson() => {
    'bannerItems': bannerItems.map(_itemToCacheJson).toList(),
    'savedAt': savedAt.toUtc().toIso8601String(),
  };
}

Map<String, dynamic> _itemToCacheJson(EmbyRawItem item) => {
  ...item.extra,
  ...item.toJson(),
};

class HomePageCacheStore {
  final Directory _directory;
  final Map<String, AsyncLock> _overviewLocks = {};
  final Map<String, AsyncLock> _bannerLocks = {};

  HomePageCacheStore({required this._directory});

  Future<HomeOverviewCacheData?> readOverview(
    String serverId,
    String userId,
  ) async {
    final file = _overviewFile(serverId, userId);
    return readJsonCacheFile(file, HomeOverviewCacheData.fromJson);
  }

  Future<HomeBannerCacheData?> readBanner(
    String serverId,
    String userId,
  ) async {
    final file = _bannerFile(serverId, userId);
    return readJsonCacheFile(file, HomeBannerCacheData.fromJson);
  }


  Future<void> updateOverviewFields(
    String serverId,
    String userId, {
    List<EmbyRawLibrary>? libraries,
    List<EmbyRawItem>? resumeItems,
    Map<String, List<EmbyRawItem>>? latestByLibrary,
  }) async {
    final lock = _overviewLocks.putIfAbsent(
      _cacheKey(serverId, userId),
      AsyncLock.new,
    );
    await lock.run(() async {
      final file = _overviewFile(serverId, userId);
      final existing =
          await readJsonCacheFile(file, HomeOverviewCacheData.fromJson) ??
          HomeOverviewCacheData.empty();
      final updated = existing.copyWith(
        libraries: libraries,
        resumeItems: resumeItems,
        latestByLibrary: latestByLibrary,
      );
      await writeJsonCacheFile(file, updated.toJson());
    });
  }

  Future<void> writeBanner(
    String serverId,
    String userId,
    HomeBannerCacheData data,
  ) async {
    final lock = _bannerLocks.putIfAbsent(
      _cacheKey(serverId, userId),
      AsyncLock.new,
    );
    await lock.run(() async {
      final file = _bannerFile(serverId, userId);
      await writeJsonCacheFile(file, data.toJson());
    });
  }

  String _cacheKey(String serverId, String userId) =>
      '${sanitizeCacheKeyComponent(serverId)}_'
      '${sanitizeCacheKeyComponent(userId)}';

  File _overviewFile(String serverId, String userId) => File(
    '${_directory.path}/home_overview_cache_${_cacheKey(serverId, userId)}.json',
  );

  File _bannerFile(String serverId, String userId) => File(
    '${_directory.path}/home_banner_cache_${_cacheKey(serverId, userId)}.json',
  );
}
