import 'dart:convert';
import 'dart:io';

import 'package:design/design.dart';
import 'package:guru_rating/core/dialog_utils.dart';
import 'package:guru_rating/core/mixins.dart';
import 'package:guru_rating/core/models.dart';
import 'package:guru_rating/core/rating_property.dart';
import 'package:guru_rating/page/rating_page.dart';
import 'package:guru_rating/page/rating_result.dart';
import 'package:guru_rating/page/rating_style.dart';
import 'package:guru_utils/analytics/analytics.dart';
import 'package:guru_utils/datetime/datetime_utils.dart';
import 'package:guru_utils/log/log.dart';
import 'package:guru_utils/property/app_property.dart';
import 'package:guru_utils/remote/remote_config.dart';
import 'package:in_app_review/in_app_review.dart';

const _logTag = 'guru_rating';

/// validation string in remote config
typedef RatingValidator = Future<bool> Function(String);

/// feedback
typedef FeedbackCallback = Future Function(RatingResult);

/// Android go to Google play Store
typedef GoToGPCallback = Future Function();

class RatingGuide {
  static const defaultRemoteConfigKey = 'app_rater';

  RatingGuide(
    this.feedbackCallback,
    this.ratingStyle,
    this.iosThankYouStyle,
    this.goingStoreStyle, {
    this.goToGPCallback,
    this.propertyAware,
    this.remoteConfigAware,
    this.initialCallback,
  });

  final PropertyAware? propertyAware;
  final RemoteConfigAware? remoteConfigAware;
  final FeedbackCallback feedbackCallback;
  final GoToGPCallback? goToGPCallback;
  final RatingStyle ratingStyle;
  final IosThankYouStyle iosThankYouStyle;
  final GoingStoreStyle goingStoreStyle;
  final VoidCallback? initialCallback;

  Future<RatingResult?> start(
      {RouteSettings? routeSettings, bool forceShow = false, RatingValidator? validator}) async {
    final config = await _getRemoteConfig();
    if (config == null) {
      Log.i('config is null', tag: _logTag);
      return null;
    }
    if (!forceShow && !await _check(config, validator)) {
      return null;
    }

    if (propertyAware != null) {
      await propertyAware!.refreshLatestPopRateGuideTime();
    } else {
      await AppProperty.getInstance().refreshLatestPopRateGuideTime();
    }

    // show rating guide dialog
    final result = await showRatingDialog(config, routeSettings: routeSettings);
    if (result != null) {
      if (!result.maybeRated && result.submit && result.feedback.isNotEmpty) {
        feedbackCallback.call(result);
      }
    }
    return result;
  }

  Future<bool> _check(RatingModel config, RatingValidator? validator) async {
    final isRated = await propertyAware?.isRated() ?? await AppProperty.getInstance().isRated();

    if (isRated) {
      Log.i('Rated!', tag: _logTag);
      return false;
    }
    final now = DateTime.now().millisecondsSinceEpoch;
    final firstInstallTime =
        await propertyAware?.getFirstInstallTime() ?? await _getFirstInstallTime();
    final gapInMillis = Duration(days: config.gapDays).inMilliseconds;
    if (now - firstInstallTime < gapInMillis) {
      Log.d('Rate now[$now] - firstInstallTime[$firstInstallTime] < gapInMillis[$gapInMillis]',
          tag: _logTag);
      return false;
    }

    final int lastPopTime = await propertyAware?.getLatestPopRateGuideTime() ??
        await AppProperty.getInstance().getLatestPopRateGuideTime();
    final nextPopIntervalInMillis = Duration(days: config.lastPopGapDays).inMilliseconds;
    if (now - lastPopTime < nextPopIntervalInMillis) {
      Log.d(
          'Rate now[$now] - lastPopTime[$lastPopTime] < nextPopIntervalInMillis[$nextPopIntervalInMillis]',
          tag: _logTag);
      return false;
    }

    if (await validator?.call(config.validation) == false) {
      Log.d('Rate now invalidate validation:${config.validation}', tag: _logTag);
      return false;
    }
    return true;
  }

  _goRating(RatingModel config) async {
    bool ratingFinished = false;
    try {
      if (config.mode == RatingModel.modeUrl && Platform.isAndroid) {
        await goToGPCallback?.call();
        ratingFinished = true;
      } else {
        if (await InAppReview.instance.isAvailable()) {
          await InAppReview.instance.requestReview();
          ratingFinished = true;
        } else {
          Log.w('in app review failed , not available', tag: _logTag);
        }
      }
    } catch (e, s) {
      AnalyticsUtils.instance.logException(e, stacktrace: s);
    }
    if (ratingFinished) {
      if (propertyAware != null) {
        propertyAware!.setRated();
      } else {
        AppProperty.getInstance().setRated();
      }
    }
  }

  Future<RatingResult?> showRatingDialog(RatingModel config, {RouteSettings? routeSettings}) async {
    return (await DialogUtils.showDialog(
      routeSettings: routeSettings,
      barrierColor: ratingStyle.barrierColor,
      widget: GestureDetector(
        onTap: () => FocusManager.instance.primaryFocus?.unfocus(),
        child: Scaffold(
        backgroundColor: Colors.transparent,
        body: Center(
          child: RatingPage(
            ratingStyle: ratingStyle,
            iosThankYouStyle: iosThankYouStyle,
            goingStoreStyle: goingStoreStyle,
            initialCallback: initialCallback,
            goingStoreCallback: () => _goRating(config),
            iosFiveStarCallback: () => _goRating(config),
          ),
        ),
      ),  
      ),
    )) as RatingResult?;
  }

  Future<RatingModel?> _getRemoteConfig() async {
    final config = await remoteConfigAware?.getRateConfig();
    if (config != null) {
      return config;
    }
    return getGuruRatingModel();
  }

  static RatingModel? getGuruRatingModel() {
    String? json = RemoteConfigUtils.instance.getString(defaultRemoteConfigKey);
    if (json?.isNotEmpty ?? false) {
      return RatingModel.fromJson(jsonDecode(json!));
    }
    return null;
  }

  Future<int> _getFirstInstallTime() async {
    // TODO hardcode
    return await AppProperty.getInstance().getOrCreateInt(
        const PropertyKey.general('first_install_time'), DateTimeUtils.currentTimeInMillis());
  }
}
