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

import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/services.dart';
import 'package:guru_app/account/account_manager.dart';
import 'package:guru_app/analytics/guru_analytics.dart';
import 'package:guru_app/guru_app.dart';
import 'package:guru_app/lifecycle/lifecycle_model.dart';
import 'package:guru_app/property/app_property.dart';
import 'package:guru_app/property/property_keys.dart';
import 'package:guru_utils/controller/lifecycle_controller.dart';
import 'package:guru_utils/lifecycle/lifecycle_manager.dart';
import 'package:guru_utils/router/router.dart';
import 'package:guru_utils/extensions/extensions.dart';
import 'package:guru_utils/log/log.dart';
import 'package:guru_utils/math/math_utils.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:json_annotation/json_annotation.dart';

/// Created by Haoyi on 5/14/21
//
Future<dynamic> _backgroundMessageHandler(RemoteMessage message) async {
  Log.d("_backgroundMessageHandler:${message.data} ${message.data["uri"]}");
  return;
}

enum RationaleResult { skip, allow }

enum PromptTrigger {
  @JsonValue(0)
  rationale, // 依赖Android原生的shouldShowRequestRationale返回值来展示对应的Rationale页面
  @JsonValue(1)
  request // 依赖请求的次数来展示对应的Rationale页面
}

class RemoteMessagingManager {
  static RemoteMessagingManager instance = RemoteMessagingManager._();

  late FirebaseMessaging _firebaseMessaging;

  final BehaviorSubject<String?> fcmToken = BehaviorSubject.seeded(null);

  Stream<String?> get observableFCMToken => fcmToken.stream;

  RemoteMessagingManager._();

  int _retryFetchTokenCount = 0;

  final _statusMap = {
    AuthorizationStatus.authorized: "granted",
    AuthorizationStatus.denied: "denied",
    AuthorizationStatus.provisional: "provisional",
    AuthorizationStatus.notDetermined: "not_determined"
  };

  String? _getUriLastSegment(String? uri) {
    try {
      return Uri.parse(uri ?? "").pathSegments.last;
    } catch (error) {
      return null;
    }
  }

  void fetchToken({Completer<String>? completer}) async {
    Log.d("Fetch FCMToken!!");
    String? token;
    try {
      token = await _firebaseMessaging.getToken();
      if (token != null) {
        fcmToken.addEx(token);
        // RuntimeProperty.instance.setString("firebase_push_token", token);
        Log.d("### FCMToken :$token");
      }
    } catch (error, stacktrace) {
      Log.d("fetchToken error!", error: error, stackTrace: stacktrace);
    }
    if (token == null || token == '') {
      final intervalSeconds =
          (MathUtils.fibonacci(_retryFetchTokenCount) * 8).clamp(8, 600);
      Future.delayed(Duration(seconds: intervalSeconds), () {
        fetchToken();
      });
      _retryFetchTokenCount++;
    } else {
      _retryFetchTokenCount = 0;
      completer?.complete(Future.value(token));
    }
  }

  Future<String?> getToken() async {
    final result = fcmToken.value ?? (await _firebaseMessaging.getToken());
    if (result != null && fcmToken.value == null) {
      fcmToken.addEx(result);
    }
    return result;
  }

  void init() async {
    _firebaseMessaging = FirebaseMessaging.instance;
    final granted = await checkNotificationPermission();
    if (!granted) {
      Future.delayed(const Duration(seconds: 8), () async {
        if (GuruApp
            .instance.appSpec.deployment.autoRequestNotificationPermission) {
          Log.d("guru_app auto request notification permissions!");
          requestNotificationPermission();
        } else {
          Log.d("guru_app check notification permissions!");
          final shouldShowRequestRationale =
              await Permission.notification.shouldShowRequestRationale;
          Log.d(
              "guru_app post request notification permission event! shouldShowRequestRationale:$shouldShowRequestRationale");
          LifecycleManager.instance.postEvent(
              RequestNotificationPermissionEvent(
                  rationale: shouldShowRequestRationale));
        }
      });
    }

    FirebaseMessaging.instance.getInitialMessage().then((message) {
      if (message != null) {
        final uri = message.data["uri"];
        if (uri != null && uri is String && uri.isNotEmpty) {
          Log.d("getInitialMessage:${message.data} $uri");
          RouteCenter.instance.dispatchUri(Uri.parse(uri));
        }
      }
    });
    FirebaseMessaging.onMessage.listen((message) {
      Log.d("onMessage:${message.data}");
      final data = message.data;

      // final notification = message.notification;
      // if (Platform.isAndroid && notification != null) {
      //   NotificationChannel.showNotification(
      //       NotificationChannel.pushType, {"title": notification.title, "body": notification.body, "cmd": data["cmd"], "uri": data["uri"]});
      // }
      GuruAnalytics.instance.logEventEx("push_receive",
          itemCategory: data["cmd"], itemName: _getUriLastSegment(data["uri"]));
    });

    FirebaseMessaging.onMessageOpenedApp.listen((message) {
      final uri = message.data["uri"];
      if (uri != null && uri is String && uri.isNotEmpty) {
        Log.d("onMessageOpenApp:${message.data} ${message.data["uri"]}");
        RouteCenter.instance.dispatchUri(Uri.parse(message.data["uri"]));
      }
    });
    _firebaseMessaging.onTokenRefresh.listen((event) {
      Log.d("onTokenRefresh $event");
      AccountManager.instance.refreshFcmToken();
      // Injector.provide<AccountService>().refreshFcmToken();
    });
    fetchToken();
  }

  Future<bool> checkNotificationPermission() async {
    final _map = {
      AuthorizationStatus.authorized: "granted",
      AuthorizationStatus.denied: "denied",
      AuthorizationStatus.provisional: "provisional",
      AuthorizationStatus.notDetermined: "not_determined"
    };
    final notificationSettings =
        await _firebaseMessaging.getNotificationSettings();
    final property = _map[notificationSettings.authorizationStatus];
    if (property != null) {
      GuruAnalytics.instance.setUserProperty("noti_perm", property);
    } else {
      GuruAnalytics.instance.setUserProperty("noti_perm", "not_determined");
    }
    return notificationSettings.authorizationStatus ==
        AuthorizationStatus.authorized;
  }

  Future<AuthorizationStatus> getNotificationAuthorizationStatus() async {
    final notificationSettings =
        await _firebaseMessaging.getNotificationSettings();
    return notificationSettings.authorizationStatus;
  }

  Future<bool> isShouldShowRequestRationale() async {
    if (GuruApp
            .instance.appSpec.deployment.notificationPermissionPromptTrigger ==
        PromptTrigger.rationale) {
      return await Permission.notification.shouldShowRequestRationale;
    }

    if (await Permission.notification.isGranted) {
      return false;
    }
    final permanentlyDenied = await Permission.notification.isPermanentlyDenied;
    if (permanentlyDenied) {
      return false;
    }
    int deniedTimes = await AppProperty.getInstance()
        .getInt(PropertyKeys.deniedNotificationPermissionTimes, defValue: 0);
    if (deniedTimes >= 2) {
      return false;
    }

    final requestTimes = await AppProperty.getInstance()
        .getInt(PropertyKeys.requestNotificationPermissionTimes, defValue: 0);
    return requestTimes >= 1;
  }

  Future<bool> _requestNotificationPermissionForAndroid(
      {String style = "default",
      String scene = "",
      Completer<RationaleResult> Function()? showRationale}) async {
    final PromptTrigger promptTrigger =
        GuruApp.instance.appSpec.deployment.notificationPermissionPromptTrigger;

    if (await Permission.notification.isGranted) {
      GuruAnalytics.instance.setUserProperty("noti_perm", "granted");
      return true;
    } else {
      final permanentlyDenied =
          await Permission.notification.isPermanentlyDenied;
      if (permanentlyDenied) {
        GuruAnalytics.instance.setUserProperty("noti_perm", "denied");
        return false;
      }
      int deniedTimes = await AppProperty.getInstance()
          .getInt(PropertyKeys.deniedNotificationPermissionTimes, defValue: 0);
      if (deniedTimes >= 2) {
        GuruAnalytics.instance.setUserProperty("noti_perm", "denied");
        return false;
      }

      final promptTriggerValue =
          promptTrigger == PromptTrigger.rationale ? "a" : "b";

      final requestTimes = await AppProperty.getInstance().increaseAndGet(
          PropertyKeys.requestNotificationPermissionTimes,
          defValue: 0);

      final trackingNotificationPermissionPass = GuruApp
              .instance.appSpec.deployment.trackingNotificationPermissionPass &&
          requestTimes <
              (GuruApp.instance.appSpec.deployment
                  .trackingNotificationPermissionPassLimitTimes);

      if (trackingNotificationPermissionPass) {
        GuruAnalytics.instance.logEventEx("noti_perm_req_$requestTimes",
            itemCategory: style,
            itemName: scene,
            parameters: {
              "request_times": requestTimes,
              "denied_times": deniedTimes,
              "prompt_trigger": promptTriggerValue
            });
      }

      final shouldShowRequestRationale =
          await Permission.notification.shouldShowRequestRationale ||
              (promptTrigger == PromptTrigger.request && requestTimes > 1);

      Log.d(
          "_requestNotificationPermission requestTimes:$requestTimes deniedTimes:$deniedTimes trackingNotificationPermissionPass:$trackingNotificationPermissionPass promptTrigger:$promptTrigger shouldShowRequestRationale:$shouldShowRequestRationale ");

      if (shouldShowRequestRationale && showRationale != null) {
        GuruAnalytics.instance.logEventEx("noti_perm_rationale_imp",
            itemCategory: style, itemName: scene);
        RationaleResult rationaleResult = RationaleResult.skip;
        try {
          final completer = showRationale();
          rationaleResult = await completer.future;
        } catch (error, stacktrace) {
          Log.d("showRationale error!", error: error, stackTrace: stacktrace);
        }
        GuruAnalytics.instance.logEventEx("noti_perm_rationale_result",
            itemCategory: style,
            itemName: scene,
            parameters: {
              "result":
                  rationaleResult == RationaleResult.allow ? "allow" : "skip",
            });
        if (rationaleResult == RationaleResult.skip) {
          return false;
        }
      }

      final showTimes = await AppProperty.getInstance().increaseAndGet(
          PropertyKeys.showNotificationPermissionTimes,
          defValue: 0);

      GuruAnalytics.instance.logEventEx("noti_perm_imp",
          itemCategory: style,
          itemName: scene,
          parameters: {
            "show_times": showTimes,
            "request_times": requestTimes,
            "denied_times": deniedTimes,
            "prompt_trigger": promptTriggerValue
          });
      final requestSettings = await _firebaseMessaging.requestPermission();
      final result =
          _statusMap[requestSettings.authorizationStatus] ?? "not_determined";
      await GuruAnalytics.instance.setUserProperty("noti_perm", result);

      if (requestSettings.authorizationStatus !=
          AuthorizationStatus.authorized) {
        final shouldShowRequestRationale2 =
            await Permission.notification.shouldShowRequestRationale;
        if (deniedTimes == 0 && shouldShowRequestRationale2) {
          deniedTimes = 1;
          await AppProperty.getInstance()
              .setInt(PropertyKeys.deniedNotificationPermissionTimes, 1);
        } else if (deniedTimes == 1 &&
            shouldShowRequestRationale != shouldShowRequestRationale2) {
          deniedTimes = 2;
          await AppProperty.getInstance()
              .setInt(PropertyKeys.deniedNotificationPermissionTimes, 2);
        }
      } else {
        if (trackingNotificationPermissionPass) {
          GuruAnalytics.instance.logEventEx("noti_perm_pass_$requestTimes",
              itemCategory: style,
              itemName: scene,
              parameters: {
                "show_times": showTimes,
                "request_times": requestTimes,
                "denied_times": deniedTimes,
                "prompt_trigger": promptTriggerValue
              });
        }
      }

      GuruAnalytics.instance.logEventEx("noti_perm_result",
          itemCategory: style,
          itemName: scene,
          parameters: {
            "result": result,
            "show_times": showTimes,
            "request_times": requestTimes,
            "denied_times": deniedTimes,
            "prompt_trigger": promptTriggerValue
          });

      Log.d(
          "notificationSettings.authorizationStatus:${requestSettings.authorizationStatus} showTimes:$requestTimes deniedTimes:$deniedTimes promptTrigger: $promptTrigger");
      return requestSettings.authorizationStatus ==
          AuthorizationStatus.authorized;
    }
  }

  Future<bool> _requestNotificationPermissionForIOS(
      {String style = "default", String scene = ""}) async {
    final status = await getNotificationAuthorizationStatus();
    switch (status) {
      case AuthorizationStatus.authorized:
        GuruAnalytics.instance.setUserProperty("noti_perm", "granted");
        return true;
      case AuthorizationStatus.provisional:
        GuruAnalytics.instance.setUserProperty("noti_perm", "provisional");
        return true;
      case AuthorizationStatus.denied:
        GuruAnalytics.instance.setUserProperty("noti_perm", "denied");
        return false;
      default:
        break;
    }

    final trackingNotificationPermissionPass =
        GuruApp.instance.appSpec.deployment.trackingNotificationPermissionPass;

    int deniedTimes = await AppProperty.getInstance()
        .getInt(PropertyKeys.deniedNotificationPermissionTimes, defValue: 0);

    final requestTimes = await AppProperty.getInstance().increaseAndGet(
        PropertyKeys.requestNotificationPermissionTimes,
        defValue: 0);
    final showTimes = await AppProperty.getInstance().increaseAndGet(
        PropertyKeys.showNotificationPermissionTimes,
        defValue: 0);

    if (trackingNotificationPermissionPass) {
      GuruAnalytics.instance.logEventEx("noti_perm_req_$requestTimes",
          itemCategory: style,
          itemName: scene,
          parameters: {
            "request_times": requestTimes,
            "denied_times": deniedTimes,
            "prompt_trigger": "a"
          });
    }

    final requestSettings = await _firebaseMessaging.requestPermission();
    final result =
        _statusMap[requestSettings.authorizationStatus] ?? "not_determined";
    await GuruAnalytics.instance.setUserProperty("noti_perm", result);

    if (requestSettings.authorizationStatus != AuthorizationStatus.authorized) {
      deniedTimes += 1;
    } else {
      if (trackingNotificationPermissionPass) {
        GuruAnalytics.instance.logEventEx("noti_perm_pass_$requestTimes",
            itemCategory: style,
            itemName: scene,
            parameters: {
              "show_times": showTimes,
              "request_times": requestTimes,
              "denied_times": deniedTimes,
              "prompt_trigger": "a"
            });
      }
    }

    GuruAnalytics.instance.logEventEx("noti_perm_result",
        itemCategory: style,
        itemName: scene,
        parameters: {
          "result": result,
          "show_times": showTimes,
          "request_times": requestTimes,
          "denied_times": deniedTimes,
          "prompt_trigger": "a"
        });
    Log.d(
        "notificationSettings.authorizationStatus:${requestSettings.authorizationStatus} showTimes:$requestTimes deniedTimes:$deniedTimes");
    return requestSettings.authorizationStatus ==
        AuthorizationStatus.authorized;
  }

  Future<bool> requestNotificationPermission(
      {String style = "default",
      String scene = "",
      Completer<RationaleResult> Function()? showRationale}) async {
    if (Platform.isAndroid) {
      return _requestNotificationPermissionForAndroid(
          style: style, scene: scene, showRationale: showRationale);
    } else if (Platform.isIOS) {
      return _requestNotificationPermissionForIOS(style: style, scene: scene);
    }
    return false;
  }

  void saveTokenToClipboard() {
    final token = fcmToken.value;
    if (token != null) {
      Clipboard.setData(ClipboardData(text: token));
      Log.d("saveTokenToClipboard:$token");
    }
  }

  void dispose() {
    fcmToken.close();
  }
}
