import 'package:flutter/services.dart';
import 'dart:io';
import 'package:guru_analytics_flutter/events_constants.dart';
import 'guru_statistic.dart';

/// Created by Haoyi on 2022/11/25

class EventPriority {
  static const EMERGENCE = 0;
  static const HIGH = 5;
  static const DEFAULT = 10;
  static const LOW = 15;
}

class AnalyticsCode {
  static const STATE_INITIALIZED = 1; // SDK 初始化完成
  static const STATE_START_WORK = 2; // 准备完毕， 等待条件满足开始上报
  static const INIT_DEVICE_INFO = 11; // deviceInfo 设置完成
  static const DELETE_EXPIRED = 12; // 删除过期事件
  static const UPLOAD_SUCCESS = 13; // 上报事件成功
  static const UPLOAD_FAIL = 14; // 上报事件失败
  static const PERIODIC_WORK_ENQUEUE = 15; // 开启PeriodicWork
  static const NETWORK_AVAILABLE = 21; // 网络状态可用
  static const NETWORK_LOST = 22; // 网络状态不可用
  static const LIFECYCLE_START = 23; // app可见
  static const LIFECYCLE_PAUSE = 24; // app不可见
  static const ERROR_API = 101; // 调用api出错
  static const ERROR_RESPONSE = 102; // api返回结果错误
  static const ERROR_CACHE_CONTROL = 103; // 设置cacheControl出错
  static const ERROR_DELETE_EXPIRED = 104; // 删除过期事件出错
  static const ERROR_LOAD_MARK = 105; // 从数据库取事件以及更改事件状态为正在上报出错
  static const ERROR_DNS = 106; // dns 错误
  static const EVENT_FIRST_OPEN = 1001; // first_open 事件
  static const EVENT_FG = 1002; // fg 事件
}

typedef PriorityGetter = int Function(String name, Map<String, dynamic> parameters);

typedef AnalyticsCallback = void Function(int eventCode, String? errorInfo);

class GuruEventLogger {
  static const MethodChannel _channel = MethodChannel('flutter.guru.guru_analytics_flutter');

  static final RegExp _nonAlphaNumeric = RegExp('[^a-zA-Z0-9_]');
  static final RegExp _alpha = RegExp('[a-zA-Z]');

  static GuruEventLogger instance = GuruEventLogger._();

  static bool _initialized = false;

  PriorityGetter _priorityGetter = _defaultPriorityGetter;

  AnalyticsCallback _analyticsCallback = (eventCode, errorInfo) {};

  GuruEventLogger._();

  Future<String> getAppInstanceId() async {
    return await _channel.invokeMethod(Method.getAppInstanceId);
  }

  Future<bool> setConsents(Map<String, bool> consents) async {
    return await _channel.invokeMethod(Method.setConsents, consents);
  }

  static int _defaultPriorityGetter(String name, Map<String, dynamic> parameters) {
    return EventPriority.DEFAULT;
  }

  Future initialize(
      {required String appId,
      required String deviceInfo,
      int batchLimit = 32,
      int uploadPeriodInSeconds = 60,
      int delayedInSeconds = 10,
      int eventExpiredInDays = 7,
      bool debug = false,
      AnalyticsCallback? callback}) async {
    if (!_initialized) {
      if (callback != null) {
        _analyticsCallback = callback;
      }
      _channel.setMethodCallHandler((call) => processMethodCall(call));
      _initialized = await _channel.invokeMethod<bool>(Method.initialize, <String, dynamic>{
            FieldName.batchLimit: batchLimit,
            FieldName.uploadPeriodInSeconds: uploadPeriodInSeconds,
            FieldName.delayedInSeconds: delayedInSeconds,
            FieldName.eventExpiredInDays: eventExpiredInDays,
            FieldName.DEBUG: debug,
            FieldName.X_APP_ID: appId,
            FieldName.X_DEVICE_INFO: deviceInfo
          }) ??
          false;
    }
  }

  Future processMethodCall(MethodCall methodCall) async {
    switch (methodCall.method) {
      case Method.onAnalyticsCallback:
        _analyticsCallback.call(methodCall.arguments[FieldName.EVENT_CODE] as int,
            methodCall.arguments[FieldName.EVENT_ERROR_INFO]);
        break;
      default:
        return;
    }
  }

  void setPriorityGetter(PriorityGetter priorityGetter) {
    _priorityGetter = priorityGetter;
  }

  Future logEvent(String eventName,
      {String? itemCategory,
      String? itemName,
      num? value,
      int? priority,
      Map<String, dynamic>? parameters}) {
    if (eventName.isEmpty ||
        eventName.length > 128 ||
        eventName.indexOf(_alpha) != 0 ||
        eventName.contains(_nonAlphaNumeric)) {
      throw ArgumentError.value(
        eventName,
        'name',
        'must contain 1 to 128 alphanumeric characters.',
      );
    }

    final Map<String, dynamic> mergedParams = {
      "itemCategory": itemCategory,
      "itemName": itemName,
      "value": value,
    };
    if (parameters != null) {
      mergedParams.addAll(parameters);
    }

    return _channel.invokeMethod(Method.logEvent, <String, dynamic>{
      FieldName.EVENT_NAME: eventName,
      FieldName.PARAMETERS: _filterOutNulls(mergedParams),
      FieldName.PRIORITY: priority ?? _priorityGetter(eventName, mergedParams)
    });
  }

  Future setUserProperty(String name, String value) {
    if (name.isEmpty ||
        name.length > 24 ||
        name.indexOf(_alpha) != 0 ||
        name.contains(_nonAlphaNumeric)) {
      throw ArgumentError.value(
        name,
        'name',
        'must contain 1 to 24 alphanumeric characters.',
      );
    }
    return _channel.invokeMethod(
        Method.setUserProperty, <String, dynamic>{FieldName.NAME: name, FieldName.VALUE: value});
  }

  Future<void> setDeviceId(String deviceId) {
    return _channel
        .invokeMethod(Method.setDeviceId, <String, dynamic>{FieldName.DEVICE_ID: deviceId});
  }

  Future<void> setUserId(String uid) {
    return _channel.invokeMethod(Method.setUid, <String, dynamic>{FieldName.USER_ID: uid});
  }

  Future<void> setAdjustId(String adjustId) {
    return _channel
        .invokeMethod(Method.setAdjustId, <String, dynamic>{FieldName.ADJUST_ID: adjustId});
  }

  Future<void> setAdId(String adId) {
    return _channel.invokeMethod(Method.setAdId, <String, dynamic>{FieldName.AD_ID: adId});
  }

  Future<void> setFirebaseId(String firebaseId) {
    return _channel
        .invokeMethod(Method.setFirebaseId, <String, dynamic>{FieldName.FIREBASE_ID: firebaseId});
  }

  Future<void> setScreen(String screenName) {
    return _channel.invokeMethod(Method.setScreen, <String, dynamic>{FieldName.SCREEN: screenName});
  }

  Future<String> zipLogs() async {
    final result = await _channel.invokeMethod(Method.zipLogs, <String, dynamic>{});
    if (result is String) {
      if (Platform.isAndroid) {
        return result;
      } else {
        final uri = Uri.parse(result);
        return uri.path;
      }
    }
    return "";
  }

  Future<GuruStatistic> getStatistic() async {
    final json = await _channel.invokeMethod(Method.getStatistic, <String, dynamic>{});
    return GuruStatistic.fromJson(json);
  }

  static Map<String, dynamic> _filterOutNulls(Map<String, dynamic> parameters) {
    final Map<String, dynamic> filtered = <String, dynamic>{};
    parameters.forEach((String key, dynamic value) {
      if (value != null) {
        filtered[key] = value;
      }
    });
    return filtered;
  }
}
