/// Created by Haoyi on 2023/10/9
///
part of 'challenge_calendar_page.dart';

class DailyChallengeBinding extends Bindings {
  @override
  void dependencies() {
    Get.lazyPut(() => ChallengeCalendarController());
  }
}

class DailyChallengeEvent {
  final List<DailyChallengeEntity> entities;

  DailyChallengeEvent(this.entities);
}

class CalendarModel {
  final DateTime focusDate;
  final Map<String, AwardEntity> awards;
  final Map<int, List<DailyChallengeEntity>> entities;
  final DateTime? latestAchieved;

  @override
  String toString() {
    return 'CalendarModel{focusDate: $focusDate, awards: ${awards.length}, entities: ${entities.length}, latestAchieved: $latestAchieved}';
  }

  List<DailyChallengeEntity> get currentDailyChallenges =>
      entities[DateTimeUtils.generateYearMonthDayNum(focusDate)] ?? [];

  const CalendarModel(this.focusDate, this.entities, this.awards, this.latestAchieved);

  static final CalendarModel defaultModel = CalendarModel(DateTime.now(), {}, {}, null);

  int get completedDaysInMonth => DailyChallengeManager.instance.getAchievedDays(entities);

  bool get isCompleted =>
      DailyChallengeManager.instance.getDailyProgress(focusDate, currentDailyChallenges) >= 100;

  int get totalDaysInMonth => DateTimeUtils.daysInMonth(focusDate.year, focusDate.month);

  int get month => focusDate.month;

  int get yearMonth => DateTimeUtils.generateYearMonthNum(focusDate);

  bool get isAchievedAward => DailyChallengeManager.instance.getAchieved(yearMonth) >= 0;

  DailyChallengeAccessMode get accessMode =>
      DailyChallengeManager.instance.getAccessMode(focusDate);
}

class ChallengeCalendarController extends AdsController with RewardedAware {
  final BehaviorSubject<DateTime> focusDateTimeSubject = BehaviorSubject.seeded(DateTime(0));

  final BehaviorSubject<CalendarModel> calendarModelSubject =
      BehaviorSubject.seeded(CalendarModel.defaultModel);

  final BehaviorSubject<DateTime?> latestAchievedSubject = BehaviorSubject.seeded(null);

  DateTime get currentFocusDateTime => focusDateTimeSubject.value;

  Stream<DateTime> get observableFocusDateTime => focusDateTimeSubject.stream;

  Stream<DateTime> get observableFocusYearMonthChangedDateTime =>
      observableFocusDateTime.distinct((prev, next) => DateTimeUtils.isSameMonth(prev, next));

  Stream<Map<int, List<DailyChallengeEntity>>> get observableYearMonthDailyChallengeEntities =>
      observableFocusYearMonthChangedDateTime.switchMap((focusDate) =>
          DailyChallengeManager.instance.observableDailyChallengeMonthData(focusDate));

  Stream<Map<String, AwardEntity>> get observableAwards =>
      DailyChallengeManager.instance.observableAwards;

  Stream<CalendarModel> get observableCalendarModel => calendarModelSubject.stream;

  Stream<DateTime?> get observableLatestAchievedDateTime => latestAchievedSubject.stream;

  CalendarModel get currentCalendarModel => calendarModelSubject.value;

  int get monthPageIndex => DailyChallengeManager.instance.getMonthIndex(currentFocusDateTime);

  final DateFormat monthFormat = DateFormat.MMMM();
  final DateFormat MMMMyFormat = DateFormat('MMMM y');
  final GlobalKey calendarKey = GlobalKey();

  DateTime get startDate => DailyChallengeManager.instance.config.start;

  late final PageController pageController;

  void _updateFocusDate(DateTime focusDate) async {
    final latestFocusDate = currentFocusDateTime;
    if (DateTimeUtils.isSameDay(latestFocusDate, focusDate)) {
      return;
    }

    focusDateTimeSubject.addEx(focusDate);
    latestAchievedSubject.addEx(null);
  }

  void _previousMonth() {
    final focusDate = currentFocusDateTime;
    final previousMonth = DateTime(focusDate.year, focusDate.month - 1);
    if (previousMonth.isBefore(DateTime(startDate.year, startDate.month))) {
      return;
    }
    _switchMonth(previousMonth);
  }

  void _nextMonth() {
    final focusDate = currentFocusDateTime;
    final nextMonth = DateTime(focusDate.year, focusDate.month + 1);
    if (nextMonth.isAfter(DateTime.now())) {
      return;
    }
    _switchMonth(nextMonth);
  }

  Future<DateTime> _updateFocusMonth(DateTime dateTime) async {
    final store = await DailyChallengeManager.instance
        .useLoadedStore(DateTimeUtils.generateYearMonthNum(dateTime));
    DateTime focusDate = dateTime;
    DailyChallengeEntity? focusEntity;
    for (var entity in store.dataList) {
      if (entity.status != DailyChallengeStatus.completed &&
          entity.progress > (focusEntity?.progress ?? -1)) {
        focusEntity = entity;
      }
    }

    if (focusEntity != null) {
      focusDate = DateTimeUtils.createDateTimeFromDateNum(focusEntity.yearMonthDay);
    } else {
      final now = DateTime.now();
      DateTime start = DateTime(dateTime.year, dateTime.month);
      DateTime end = DateTime(
          dateTime.year, dateTime.month, DateTimeUtils.daysInMonth(dateTime.year, dateTime.month));
      start = start.isBefore(DailyChallengeManager.instance.config.start)
          ? DailyChallengeManager.instance.config.start
          : start;
      end = end.isAfter(now) ? now : end;
      const dayDuration = Duration(days: 1);
      for (var monthDay = end;
          !monthDay.isBefore(start);
          monthDay = monthDay.subtract(dayDuration)) {
        final entity = store.getData(DateTimeUtils.yyyyMMddBuild(monthDay));
        if (entity == null) {
          focusDate = monthDay;
          break;
        }
      }
    }
    _updateFocusDate(focusDate);
    return focusDate;
  }

  void _switchMonth(DateTime dateTime) async {
    final currentFocusDate = currentFocusDateTime;

    final dstIdx = DailyChallengeManager.instance.getMonthIndex(dateTime);
    final srcIdx = DailyChallengeManager.instance.getMonthIndex(currentFocusDate);
    if (dstIdx == srcIdx) {
      return;
    }
    if ((srcIdx - dstIdx).abs() == 1) {
      pageController.animateToPage(DailyChallengeManager.instance.getMonthIndex(dateTime),
          duration: const Duration(milliseconds: 450), curve: Curves.easeInOut);
    } else {
      pageController.jumpToPage(dstIdx);
    }
  }

  List<DailyChallengeEvent> getChallengeByDateTime(DateTime? date) {
    if (date == null) {
      return [];
    }
    final entities = DailyChallengeManager.instance.getDailyChallengeByDate(date);
    return [DailyChallengeEvent(entities)];
  }

  int getDailyProgress(DateTime dateTime, List<DailyChallengeEntity> entities) {
    return DailyChallengeManager.instance.getDailyProgress(dateTime, entities);
  }

  String getFocusDateTrophyImage() {
    return getTrophyImage(currentFocusDateTime);
  }

  String getTrophyImage(DateTime dateTime) {
    return DailyChallengeManager.instance
        .getTrophyImage(DateTimeUtils.generateYearMonthNum(dateTime));
  }

  int calculateRow(int firstWeekday, int day) {
    return ((firstWeekday + day - 1) ~/ 7).toInt();
  }

  int calculateColumn(int firstWeekday, int day) {
    return ((firstWeekday + day - 1) % 7).toInt();
  }

  void clearLatestAchieved() {
    latestAchievedSubject.addEx(null);
  }

  Future<AdCause> showLoading(Completer<AdCause> loadingCompleter) async {
    final cancellableStream =
        Future.delayed(const Duration(seconds: 5)).then((value) => true).asStream();
    final completer = GuruPopup.instance.showCancelableLoading(
        cancellableStream: cancellableStream,
        onCompleted: () {
          if (!loadingCompleter.isCompleted) {
            loadingCompleter.complete(AdCause.canceled);
          }
        });

    final result = await loadingCompleter.future;
    if (!completer.isCompleted) {
      completer.complete();
    }
    return result;
  }

  Future playOrReplay(DailyChallengeAccessMode accessMode) async {
    if (accessMode == DailyChallengeAccessMode.reward) {
      final adResult = await showRewardedAd(scene: "daily_ads_play", loadingDialog: showLoading);
      if (adResult.cause != AdCause.success) {
        return;
      }
    }
    final calendarModel = currentCalendarModel;

    Log.d("before playOrReplay:${calendarModel.isCompleted}");

    DailyChallengeManager.instance.playOrReplay(
        calendarModel.focusDate, calendarModel.currentDailyChallenges, onCompleted: () {
      Log.d("onCompleted after playOrReplay:${calendarModel.isCompleted} ");
      if (!calendarModel.isCompleted) {
        latestAchievedSubject.addEx(calendarModel.focusDate);
      }
    });
  }

  Future _checkAndCollect(DateTime focusDate) async {
    await DailyChallengeManager.instance.checkAndCollect(focusDate);
    DailyChallengeManager.instance.config.callbacks?.onCalendarCollected(focusDate);
  }

  @override
  void onResumed() {
    final dateTime = DailyChallengeManager.instance.consumeFocusDateAfterPageResume();
    if (dateTime != null) {
      _switchMonth(dateTime);
    }
  }

  @override
  void onInit() {
    super.onInit();
    final now = DateTime.now();
    pageController = PageController(initialPage: DailyChallengeManager.instance.getMonthIndex(now));
    addSubscription(Rx.combineLatest4(
        observableFocusDateTime,
        observableYearMonthDailyChallengeEntities,
        observableAwards,
        observableLatestAchievedDateTime,
        (focusDate, entities, awards, latest) =>
            CalendarModel(focusDate, entities, awards, latest)).listen((model) {
      calendarModelSubject.addEx(model);
    }));
    _updateFocusDate(now);
  }
}
