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

import 'package:daily_challenge/daily_challenge_package.dart';
import 'package:daily_challenge/data/db/entities/entities.dart';
import 'package:daily_challenge/l10n/generated/app_localizations.dart';
import 'package:daily_challenge/l10n/localizations/app_strings.dart';
import 'package:daily_challenge/mgr/daily_challenge_manager.dart';
import 'package:design/design.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart' hide Rx;
import 'package:guru_popup/guru_popup.dart';
import 'package:guru_utils/ads/ads.dart';
import 'package:guru_utils/controller/ads_controller.dart';
import 'package:guru_utils/controller/aware/ads/rewarded_aware.dart';
import 'package:guru_utils/datetime/datetime_utils.dart';
import 'package:guru_utils/extensions/extensions.dart';
import 'package:guru_utils/log/log.dart';
import 'package:guru_utils/router/router.dart';
import 'package:guru_utils/widget/widget_utils.dart';
import 'package:guru_widgets/animation/standard/transform_animation.dart';
import 'package:guru_widgets/guru_widgets.dart';
import 'package:guru_widgets/theme/guru_theme.dart';
import 'package:intl/intl.dart';
import 'package:rxdart/rxdart.dart';
import 'package:table_calendar/table_calendar.dart';

part 'challenge_calendar_controller.dart';

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

@DesignSpec(width: 750, height: 672, specMode: SpecMode.nested)
abstract class CalendarDesignSpec implements BasicDesignSpec {
  @SpecWidth(37)
  double get horizontalSpacing;

  @SpecHeight(80)
  double get selectedSize;

  @SpecHeight(64)
  double get markerSize;

  @SpecAbsoluteFontSize(30)
  double get dateFontSize;

  @SpecAbsoluteFontSize(22)
  double get dayOfWeekFontSize;

  @SpecHeight(88)
  double get rowHeight;

  @SpecHeight(56)
  double get weekRowHeight;

  @SpecAbsoluteFontSize(30)
  double get dayFontSize;

  @SpecHeight(8)
  double get cellMargin;

  @SpecHeight(4)
  double get progressBarStrokeWidth;

  static CalendarDesignSpec create(Size size, {Offset offset = Offset.zero}) {
    return _CalendarDesignSpec.from(size, offset: offset);
  }
}

@DesignSpec(width: 750, height: 1624, specMode: SpecMode.useGet)
abstract class DailyChallengeDesignSpec implements BasicDesignSpec {
  @NestedSpec(750, 672)
  CalendarDesignSpec get calendarDesignSpec;

  @CombinedSpec(SpecStatusBarHeight(50), SpecVertical(0))
  double get appBarTopSpacing;

  @SpecHeight(48, consistent: true)
  double get appBarIconSize;

  @SpecHeight(72, consistent: true)
  double get appBarIconButtonSize;

  @SpecEdgeInsets.symmetric(horizontal: SpecWidth(28))
  EdgeInsets get appBarPadding;

  @SpecHeight(88, consistent: true)
  double get appBarHeight;

  @SpecAbsoluteFontSize(34)
  double get titleFontSize;

  @SpecHeight(64, consistent: true)
  double get arrowIconSize;

  @SpecHeight(96, consistent: true)
  double get arrowButtonSize;

  @SpecEdgeInsets.all(SpecHorizontal(16))
  EdgeInsets get arrowIconMargin;

  @SpecHeight(104, consistent: true)
  double get buttonHeight;

  @SpecHorizontal(60)
  double get buttonHorizontalSpacing;

  @SpecHorizontal(56)
  double get buttonBottomSpacing;

  static DailyChallengeDesignSpec get() {
    return _DailyChallengeDesignSpec.get();
  }
}

class ChallengeCalendarTheme {
  final Color? primaryColor;
  final Color? dateTextColor;
  final Color? selectedDateTextColor;
  final Color? disabledDateTextColor;
  final Color? daysOfWeekTextColor;
  final Color? headerBackgroundColor;
  final Color? calendarBackgroundColor;
  final String? awardIcon;
  final String? closeIcon;
  final String? markerIcon;
  final String? nextMonthArrowIcon;
  final String? previousMonthArrowIcon;
  final LottieAsset? animatedTrophyBackground;
  final LottieAsset? animatedAchievedTrophyBackground;
  final GuruButtonStyle? actionButtonStyle;

  const ChallengeCalendarTheme(
      {this.primaryColor,
      this.dateTextColor,
      this.selectedDateTextColor,
      this.disabledDateTextColor,
      this.daysOfWeekTextColor,
      this.headerBackgroundColor,
      this.calendarBackgroundColor,
      this.closeIcon,
      this.awardIcon,
      this.markerIcon,
      this.nextMonthArrowIcon,
      this.previousMonthArrowIcon,
      this.animatedTrophyBackground,
      this.animatedAchievedTrophyBackground,
      this.actionButtonStyle});

  static const defaultTheme = ChallengeCalendarTheme();
}

class _UIHolder {
  ThemeData? _theme;
  GuruThemeData? _guruTheme;
  ChallengeCalendarTheme? _challengeCalendarTheme;
  late DailyChallengeDesignSpec _designSpec;
  late AppLocalizations appStrings;

  _UIHolder();

  void attach(BuildContext context) {
    _theme ??= Theme.of(context);
    _guruTheme ??= GuruTheme.of(context);
    _challengeCalendarTheme ??=
        _guruTheme?.getCustomTheme<ChallengeCalendarTheme>(
                ChallengeCalendarTheme.defaultTheme.runtimeType) ??
            ChallengeCalendarTheme.defaultTheme;
    _designSpec = DailyChallengeDesignSpec.get();
    appStrings = DailyChallengeStrings.of();
  }
}

mixin _UIData {
  final _UIHolder _uiHolder = _UIHolder();

  ThemeData get theme => _uiHolder._theme ?? ThemeData();

  GuruThemeData get guruTheme => _uiHolder._guruTheme ?? GuruThemeData();

  ChallengeCalendarTheme get challengeCalendarTheme =>
      _uiHolder._challengeCalendarTheme ?? ChallengeCalendarTheme.defaultTheme;

  DailyChallengeDesignSpec get designSpec => _uiHolder._designSpec;

  CalendarDesignSpec get calendarDesignSpec => designSpec.calendarDesignSpec;

  AppLocalizations get appStrings => _uiHolder.appStrings;
}

class ChallengeCalendarPage extends GetWidget<ChallengeCalendarController>
    with _UIData {
  final bool nested;
  final navigationDesignSpec =
      GuruThemeDesignSpec.get().navigationBarDesignSpec;

  ChallengeCalendarPage({super.key, this.nested = false});

  Widget buildCalendar(CalendarModel model, BoxConstraints constraints) {
    final currentFocusDateTime = model.focusDate;
    final count = DateTimeUtils.getRowCount(currentFocusDateTime);
    final lastDay = DateTime.now();
    final calendarDesignSpec = designSpec.calendarDesignSpec;

    final primaryColor = challengeCalendarTheme.primaryColor ??
        guruTheme.colorScheme.primaryColor ??
        theme.primaryColor;
    final defaultDateTextColor =
        challengeCalendarTheme.dateTextColor ?? Colors.black;
    final selectedDateTextColor =
        challengeCalendarTheme.selectedDateTextColor ?? Colors.white;
    final disabledDateTextColor =
        challengeCalendarTheme.disabledDateTextColor ??
            Colors.grey.withOpacity(0.65);
    final daysOfWeekTextColor =
        challengeCalendarTheme.daysOfWeekTextColor ?? const Color(0xFF999999);
    final defaultTextStyle = TextStyle(
        color: defaultDateTextColor,
        fontWeight: GuruTheme.fwMedium,
        fontSize: calendarDesignSpec.dateFontSize);

    final calendarHeight = constraints.maxHeight;

    return Container(
      width: calendarDesignSpec.measuredSize.width,
      height: calendarHeight,
      padding: EdgeInsets.only(
          left: calendarDesignSpec.horizontalSpacing,
          right: calendarDesignSpec.horizontalSpacing,
          top: calendarHeight / 48 * 4,
          bottom: calendarHeight / 48 * 5),
      clipBehavior: Clip.none,
      child: TableCalendar<DailyChallengeEvent>(
        key: controller.calendarKey,
        rowHeight: calendarDesignSpec.rowHeight,
        daysOfWeekHeight: calendarDesignSpec.weekRowHeight,
        shouldFillViewport: true,
        firstDay: controller.startDate,
        lastDay: lastDay,
        headerVisible: false,
        calendarStyle: CalendarStyle(
          cellMargin: EdgeInsets.all(count == 6
              ? calendarDesignSpec.cellMargin / 2
              : calendarDesignSpec.cellMargin),
          markersAutoAligned: false,
          outsideDaysVisible: false,
          markersAlignment: Alignment.center,
          canMarkersOverflow: true,
          // tablePadding: calendarDesignSpec.padding,
          todayTextStyle: defaultTextStyle,
          defaultTextStyle: defaultTextStyle,
          weekendTextStyle: defaultTextStyle,
          outsideTextStyle: TextStyle(
              color: Colors.blue, //selectedDateTextColor,
              fontWeight: GuruTheme.fwMedium,
              fontSize: calendarDesignSpec.dateFontSize),
          withinRangeTextStyle: TextStyle(
              color: Colors.red,
              fontWeight: GuruTheme.fwMedium,
              fontSize: calendarDesignSpec.dateFontSize),
          selectedTextStyle: TextStyle(
              color: selectedDateTextColor,
              fontWeight: GuruTheme.fwMedium,
              fontSize: calendarDesignSpec.dateFontSize),
          rangeEndTextStyle: TextStyle(
              color: const Color(0xff252525),
              fontWeight: GuruTheme.fwMedium,
              fontSize: calendarDesignSpec.dateFontSize),
          todayDecoration: BoxDecoration(
            color: Colors.transparent,
            shape: BoxShape.circle,
            border: Border.all(color: Colors.transparent, width: 0),
          ),
          disabledTextStyle: TextStyle(
              color: disabledDateTextColor,
              fontWeight: GuruTheme.fwMedium,
              fontSize: calendarDesignSpec.dateFontSize),
          selectedDecoration: BoxDecoration(
            color: primaryColor,
            shape: BoxShape.circle,
            border: Border.all(color: primaryColor, width: 1),
          ),
        ),
        daysOfWeekStyle: DaysOfWeekStyle(
            weekdayStyle: TextStyle(
                color: daysOfWeekTextColor,
                fontSize: calendarDesignSpec.dayOfWeekFontSize,
                fontWeight: GuruTheme.fwMedium),
            weekendStyle: TextStyle(
                color: daysOfWeekTextColor,
                fontSize: calendarDesignSpec.dayOfWeekFontSize,
                fontWeight: GuruTheme.fwMedium)),
        calendarBuilders: CalendarBuilders(
            dowBuilder: (BuildContext context, DateTime day) {
              final weekdayString = DateFormat.E('en').format(day)[0];
              return Center(
                child: Text(
                  weekdayString,
                  style: TextStyle(
                      color: daysOfWeekTextColor,
                      fontSize: calendarDesignSpec.dayOfWeekFontSize,
                      fontWeight: GuruTheme.fwMedium),
                ),
              );
            },
            singleMarkerBuilder: _markerBuilder),
        eventLoader: controller.getChallengeByDateTime,
        focusedDay: currentFocusDateTime,
        selectedDayPredicate: (day) {
          return DateTimeUtils.isSameDay(currentFocusDateTime, day);
        },
        onDaySelected: (selectedDay, focusedDay) {
          controller._updateFocusDate(focusedDay);
        },
        onPageChanged: (focusedDay) {
          // No need to call `setState()` here
          Log.d("onPageChanged focusedDay:$focusedDay");
          controller._switchMonth(focusedDay);
        },
      ),
    );
  }

  Widget? _markerBuilder(
      BuildContext context, DateTime date, DailyChallengeEvent event) {
    final entities = event.entities;
    final progress = controller.getDailyProgress(date, entities).clamp(0, 100);

    if (entities.isEmpty || progress <= 0) {
      return const EmptySpacer();
    }

    final isSelected =
        DateTimeUtils.isSameDay(date, controller.currentFocusDateTime);
    if (progress > 0 && progress < 100) {
      final primaryContentColor = challengeCalendarTheme.primaryColor ??
          guruTheme.colorScheme.primaryColor ??
          theme.primaryColor;
      final selectedContentColor =
          challengeCalendarTheme.selectedDateTextColor ??
              guruTheme.colorScheme.containerColor ??
              Colors.white;
      return SizedBox(
        width: calendarDesignSpec.markerSize,
        height: calendarDesignSpec.markerSize,
        child: CircularProgressIndicator(
          color: isSelected ? selectedContentColor : primaryContentColor,
          value: progress / 100.0,
          strokeWidth: calendarDesignSpec.progressBarStrokeWidth,
          backgroundColor: isSelected
              ? selectedContentColor.withOpacity(0.2)
              : primaryContentColor.withOpacity(0.2),
        ),
      );
    }
    final icon = challengeCalendarTheme.markerIcon;
    final marker = icon != null
        ? Image.asset(
            icon,
            width: calendarDesignSpec.markerSize,
            height: calendarDesignSpec.markerSize,
          )
        : DecoratedBox(
            decoration: const BoxDecoration(
                color: Colors.white, shape: BoxShape.circle),
            child: Icon(Icons.stars,
                size: calendarDesignSpec.markerSize,
                color: Colors.orangeAccent),
          );
    return DateTimeUtils.isSameDay(
            controller.currentCalendarModel.latestAchieved, date)
        ? achievedMarker(marker)
        : marker;
  }

  void showAchievedMarkerOverlay(Rect anchorBoundary,
      {required Widget marker, VoidCallback? onCompleted}) {
    final completer = Completer();
    OverlayEntry? overlayEntry = OverlayEntry(
        builder: (BuildContext context) => Positioned(
              top: anchorBoundary.top,
              left: anchorBoundary.left,
              child: AnimatedTransformBuilder.single(
                fromScale: 2.0,
                toScale: 1.0,
                alignment: Alignment.center,
                curve: const Cubic(0.175, 0.885, 0.32, 1.1),
                duration: const Duration(milliseconds: 450),
                child: marker,
                onCompleted: () {
                  completer.complete();
                },
              ),
            ));
    final navigatorState =
        Navigator.of(Get.overlayContext!, rootNavigator: false);
    final overlayState = navigatorState.overlay!;
    overlayState.insert(overlayEntry);

    completer.future.then((_) {
      overlayEntry?.remove();
      overlayEntry = null;
    }).whenComplete(() {
      overlayEntry?.remove();
      overlayEntry = null;
      controller.clearLatestAchieved();
      onCompleted?.call();
    }).catchError((error) {
      overlayEntry?.remove();
      overlayEntry = null;
      controller.clearLatestAchieved();
    });
  }

  // 这里没有直接使用AnimatedTransformBuilder的原因是由于该设计边框过窄，在做一些缩放动画时会被上层clip掉
  // 因此这里使用Overlay的办法完成相应的动画
  Widget achievedMarker(Widget marker) {
    final GlobalKey key = GlobalKey();
    return AnimatedTransformBuilder.single(
        fromOpacity: 0.0,
        toOpacity: 1.0,
        duration: const Duration(milliseconds: 5),
        onCompleted: () {
          final boundary = WidgetUtils.getWidgetBoundary(key);
          showAchievedMarkerOverlay(boundary, marker: marker, onCompleted: () {
            controller._checkAndCollect(controller.currentFocusDateTime);
          });
        },
        child: SizedBox.square(
            key: key, dimension: calendarDesignSpec.markerSize));
  }

  Widget buildActionButton(
      {String? iconResUri, IconData? iconData, VoidCallback? onTap}) {
    return TapWidget(
      inkWell: true,
      shape: const CircleBorder(),
      onTap: () {
        onTap?.call();
      },
      child: SizedBox.square(
        dimension: designSpec.appBarIconButtonSize,
        child: Center(
            child: iconResUri != null
                ? AdaptiveImage(
                    iconResUri,
                    width: designSpec.appBarIconSize,
                    height: designSpec.appBarIconSize,
                    color: Colors.white,
                  )
                : Icon(
                    iconData,
                    size: designSpec.appBarIconSize,
                    color: Colors.white,
                  )),
      ),
    );
  }

  Widget buildArrowButton(
      {String? iconResUri, IconData? iconData, VoidCallback? onTap}) {
    return TapWidget(
        inkWell: true,
        shape: const CircleBorder(),
        onTap: () {
          onTap?.call();
        },
        child: SizedBox.square(
            dimension: designSpec.arrowButtonSize,
            child: Center(
              child: iconResUri != null
                  ? AdaptiveImage(
                      iconResUri,
                      width: designSpec.arrowIconSize,
                      height: designSpec.arrowIconSize,
                      color: Colors.white,
                    )
                  : Icon(
                      iconData,
                      size: designSpec.arrowIconSize,
                      color: Colors.white,
                    ),
            )));
  }

  Widget buildLottie(bool achieved, Size size) {
    // final lottieSize = headerDesignSpec.measuredSize;
    final lottieAsset = achieved
        ? (challengeCalendarTheme.animatedAchievedTrophyBackground ??
            challengeCalendarTheme.animatedTrophyBackground)
        : challengeCalendarTheme.animatedTrophyBackground;
    if (lottieAsset == null) {
      return const EmptySpacer();
    }
    return LottieWidget(
      uri: lottieAsset.url,
      duration: lottieAsset.duration,
      period: lottieAsset.duration,
      action: LottieAction.forwardAndRepeat,
      width: size.width,
      height: size.height,
      package: lottieAsset.package,
    );
  }

  Widget buildTrophyPager(Size headerSize) {
    final theme = challengeCalendarTheme;
    final trophyHeight =
        (headerSize.height - designSpec.appBarTopSpacing) * 0.67;
    final topPadding = (headerSize.height - trophyHeight) / 33 * 25;
    final bottomPadding = (headerSize.height - trophyHeight) / 33 * 8;
    return PageView.builder(
      scrollDirection: Axis.horizontal,
      itemCount: DailyChallengeManager.instance.validMonths.length,
      controller: controller.pageController,
      onPageChanged: (index) {
        if (controller.monthPageIndex == index) {
          return;
        }
        final date = DailyChallengeManager.instance.validMonths[index];
        if (!DateTimeUtils.isSameMonth(date, controller.currentFocusDateTime)) {
          controller._updateFocusMonth(date);
        }
      },
      itemBuilder: (ctx, index) {
        final dateTime = DailyChallengeManager.instance.validMonths[index];
        return Container(
          padding: EdgeInsets.only(top: topPadding, bottom: bottomPadding),
          child: AdaptiveImage(controller.getTrophyImage(dateTime),
              width: trophyHeight, height: trophyHeight),
        );
      },
    );
  }

  List<Widget> buildArrows(bool leftVisible, bool rightVisible, Size headerSize) {
    final guruTheme = this.guruTheme;
    final theme = challengeCalendarTheme;
    final trophyHeight =
        (headerSize.height - designSpec.appBarTopSpacing) * 0.67;
    final bottomPadding = (headerSize.height - trophyHeight) / 33 * 8;
    final bottomSpacing = (trophyHeight / 2) - designSpec.arrowIconMargin.bottom - (designSpec.arrowButtonSize / 2);

    return [
      if (leftVisible)
        Positioned(
            left: 0,
            bottom: bottomSpacing + bottomPadding,
            child: Padding(
                padding: designSpec.arrowIconMargin,
                child: buildArrowButton(
                    iconData: Icons.arrow_back_ios_new,
                    iconResUri: theme.previousMonthArrowIcon ??
                        guruTheme.iconScheme.leftArrowIcon,
                    onTap: () {
                      controller._previousMonth();
                    }))),
      if (rightVisible)
        Positioned(
            right: 0,
            bottom: bottomSpacing + bottomPadding,
            child: Padding(
                padding: designSpec.arrowIconMargin,
                child: buildArrowButton(
                    iconData: Icons.arrow_forward_ios,
                    iconResUri: theme.nextMonthArrowIcon ??
                        guruTheme.iconScheme.rightArrowIcon,
                    onTap: () {
                      controller._nextMonth();
                    })))
    ];
  }

  Widget buildHeader(CalendarModel model, BoxConstraints constraints) {
    final theme = challengeCalendarTheme;
    final focusDate = model.focusDate;
    final now = DateTime.now();
    final leftVisible = DateTime(focusDate.year, focusDate.month, 1).isAfter(
        DateTime(controller.startDate.year, controller.startDate.month, 1));
    final rightVisible = focusDate.isBefore(DateTime(now.year, now.month, 1));
    final titleFormat = focusDate.year != now.year
        ? controller.MMMMyFormat
        : controller.monthFormat;

    final headerSize = Size(constraints.maxWidth, (nested ? MediaQueryData.fromWindow(window).size.height : constraints.maxHeight) * 0.384);

    return SizedBox.fromSize(
      size: headerSize,
      child: DecoratedBox(
        decoration: BoxDecoration(
            color: challengeCalendarTheme.headerBackgroundColor ?? Colors.blue),
        child: Stack(fit: StackFit.expand, children: [
          buildLottie(model.isAchievedAward, headerSize),
          Positioned.fill(child: buildTrophyPager(headerSize)),
          Positioned(
              top: designSpec.appBarTopSpacing,
              left: 0,
              right: 0,
              child: SizedBox(
                  width: double.infinity,
                  height: designSpec.appBarHeight,
                  child: Padding(
                      padding: designSpec.appBarPadding,
                      child: Stack(
                        children: [
                          if (!nested)
                            Align(
                                alignment: Alignment.centerLeft,
                                child: buildActionButton(
                                    iconResUri: theme.closeIcon ??
                                        guruTheme.iconScheme.closeIcon,
                                    iconData: Icons.close,
                                    onTap: () {
                                      RouteCenter.instance.back();
                                    })),
                          Center(
                              child: Text(
                            titleFormat.format(model.focusDate),
                            style: TextStyle(
                                fontSize: designSpec.titleFontSize,
                                color: Colors.white,
                                fontWeight: GuruTheme.fwBold),
                          )),
                          Align(
                              alignment: Alignment.centerRight,
                              child: buildActionButton(
                                  iconData: Icons.emoji_events_outlined,
                                  iconResUri: challengeCalendarTheme.awardIcon,
                                  onTap: () {
                                    RouteCenter.instance.openPath(
                                        DailyChallengeManager
                                            .instance.config.awardRoute
                                            .path());
                                  }))
                        ],
                      )))),
          ...buildArrows(leftVisible, rightVisible, headerSize)
        ]),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    _uiHolder.attach(context);

    final paddingBottom = MediaQuery.of(context).padding.bottom;

    return Scaffold(
      backgroundColor: challengeCalendarTheme.calendarBackgroundColor ??
          guruTheme.colorScheme.containerColor,
      body: StreamBuilder<CalendarModel>(
          stream: controller.observableCalendarModel,
          builder: (context, snapshot) {
            final model = snapshot.data ?? CalendarModel.defaultModel;
            final accessMode = model.accessMode;
            Log.d("model:$model");
            return LayoutBuilder(
                builder: (BuildContext context, BoxConstraints constraints) {
              return Column(
                mainAxisSize: MainAxisSize.max,
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  buildHeader(model, constraints),
                  Expanded(child: LayoutBuilder(builder:
                      (BuildContext context, BoxConstraints constraints) {
                    return buildCalendar(model, constraints);
                  })),
                  Padding(
                      padding: EdgeInsets.only(left: designSpec.buttonHorizontalSpacing, right: designSpec.buttonHorizontalSpacing, bottom: designSpec.buttonBottomSpacing),
                      child: GuruButton(
                        size: Size(designSpec.measuredSize.width - (designSpec.buttonHorizontalSpacing * 2), designSpec.buttonHeight),
                        sizeSpec: GuruButtonSizeSpec.s1,
                        style: challengeCalendarTheme.actionButtonStyle,
                        leading: accessMode == DailyChallengeAccessMode.reward
                            ? guruTheme.iconScheme.adsVideoIcon
                            : null,
                        action: model.isCompleted
                            ? appStrings.replay
                            : appStrings.play,
                        onPressed: () async {
                          await controller.playOrReplay(accessMode);
                        },
                      )),
                  if (!nested)
                  SizedSpacer(
                      height: navigationDesignSpec.navigationBarHeight + paddingBottom)
                ],
              );
            });
          }),
    );
  }
}
