// ignore_for_file: constant_identifier_names

import 'package:camera/camera.dart' as camera;
import 'package:flutter/foundation.dart';
import 'package:google_mlkit_face_detection/google_mlkit_face_detection.dart'
    as mlkit;
import 'package:vitalapp/rpc.dart';
import 'package:vitalapp/architecture/storage/storage.dart';
import 'package:vnote_device_plugin/events/event_type.dart';
import 'package:fis_common/logger/logger.dart';

abstract class FacialRecognitionExceptionCode {
  static const ABORT = -1;
  static const VOID = 0;
  static const FACE_NOT_FOUNT = 1001;
  static const FACE_OVER_COUNT = 1002;
  static const PTHOT_TAKE_ERROR = 2001;
  static const PTHOT_TYPE_ERROR = 2002;
  static const PTHOT_UPLOAD_FAIL = 2003;
  static const RESULT_UNCHECKED = 3001;
  static const RESULT_NOT_CLEAR = 3002;
}

class FacialRecognitionException implements Exception {
  final int code;
  final String message;

  FacialRecognitionException(this.code, this.message);

  @override
  String toString() {
    return "[FacialRecognitionException] code: $code, message: $message.";
  }
}

/// 人脸识别采集器
class FacialRecognitionCapturer {
  /// 重试次数限制
  ///
  /// - 默认`10`次
  final int retryLimit;

  /// 单次识别图像已缓存事件
  final frameCachedEvent = FEventHandler<String>();

  /// 识别成功事件
  final successEvent = FEventHandler<String>();

  /// 识别失败事件
  final failEvent = FEventHandler<FacialRecognitionException>();

  /// 识别超时事件 - 超过限制次数
  final timeoutEvent = FEventHandler<void>();

  FacialRecognitionCapturer({this.retryLimit = 10});

  int _workedCount = 0;
  _Worker? _worker;
  Object? _result;
  bool _isBusy = false;
  bool _isPause = false;

  camera.CameraController? _cameraController;
  mlkit.FaceDetector? _faceDetector;

  /// 识别结果
  Object? get result => _result;

  /// 初始化
  Future<void> init(
    camera.CameraController? cameraController,
    mlkit.FaceDetector faceDetector,
  ) async {
    _cameraController = cameraController;
    _faceDetector = faceDetector;
    logger.i("FacialRecognitionCapturer init.");
  }

  /// 暂停
  void callPause() {
    _isPause = true;
    logger.i("FacialRecognitionCapturer pause.");
  }

  /// 继续
  void callContinue() {
    _isPause = false;
    logger.i("FacialRecognitionCapturer continue.");
  }

  /// 开启工作
  Future<void> run() async {
    logger.i("FacialRecognitionCapturer start run...");
    _isBusy = true;
    while (_isBusy) {
      if (_workedCount > retryLimit) {
        stop();
        timeoutEvent.emit(this, null);
        break;
      }
      if (_isPause == false) {
        _workedCount++;
        await _doOnce();
      }
      // 每次间隔100ms检测一次
      await Future.delayed(const Duration(milliseconds: 100));
    }
    logger.i("FacialRecognitionCapturer run finish.");
  }

  /// 停止工作
  void stop() {
    logger.i("FacialRecognitionCapturer stop...");
    _isBusy = false;
    _workedCount = retryLimit + 1;
    _worker?.stop();
    _worker = null;
    logger.i("FacialRecognitionCapturer stop done.");
  }

  Future<void> _doOnce() async {
    try {
      final $ = WorkVariables();
      $.cameraController = _cameraController;
      $.faceDetector = _faceDetector;
      $.imgCachedCallback = (value) {
        // 通知识别帧已缓存本地
        frameCachedEvent.emit(this, value);
      };
      _worker = _Worker($);
      await _worker!.init();
      await _worker!.run();
      _result = $.result;
      if ($.result != null) {
        // 暂停等待success回调处理后发送继续工作信号
        callPause();
        // 识别成功
        successEvent.emit(this, $.result!);
      }
    } on FacialRecognitionException catch (e) {
      if (e.code > 0) {
        failEvent.emit(this, e);
      }
    } catch (e) {
      logger.e("FacialRecognitionCapturer do once error.", e);
    }
  }
}

class _Worker {
  final steps = <Future<void> Function()>[];
  late final WorkVariables $;

  bool isContinue = false;

  _Worker(WorkVariables variables) {
    $ = variables;
    steps.add(takePhoto);
    steps.add(detectFace);
    steps.add(checkFaceNum);
    steps.add(uploadFile);
  }

  Future<void> init() async {}

  Future<void> run() async {
    isContinue = true;

    final count = steps.length;
    for (var i = 0; i < count; i++) {
      await steps[i].call();
    }

    isContinue = false;
  }

  void stop() {
    isContinue = false;
  }

  /// 执行一次拍摄
  Future<void> takePhoto() async {
    try {
      final c = $.cameraController;
      if (c == null || c.value.isInitialized == false) {
        return abortWithException(
          FacialRecognitionExceptionCode.PTHOT_TAKE_ERROR,
          "未识别到摄像头",
        );
      }
      if (c.value.isTakingPicture) {
        return abort();
      }
      final file = await c.takePicture();
      $.cachedFile = file;
      $.imgCachedCallback?.call("");
    } on camera.CameraException catch (e) {
      logger.e("FacialRecognitionCapturer._Worker takePhoto error.", e);
      // TODO: 暂时不提示
      // abortWithException(
      //   FacialRecognitionExceptionCode.PTHOT_TAKE_ERROR,
      //   e.description ?? "拍摄失败",
      // );
      abort();
    }
  }

  /// 检测一次人脸
  Future<void> detectFace() async {
    try {
      final image = mlkit.InputImage.fromFilePath($.cachedFile!.path);
      final faces = await $.faceDetector!.processImage(image);
      $.faceNum = faces.length;
    } catch (e) {
      logger.e("FacialRecognitionCapturer._Worker detectFace error.", e);
      abort();
    }
  }

  /// 检查人脸数量
  Future<void> checkFaceNum() async {
    final num = $.faceNum;
    if (num == 0) {
      return abortWithException(
        FacialRecognitionExceptionCode.FACE_NOT_FOUNT,
        "请将面部保持在识别框内",
      );
    }
    if (num > 1) {
      return abortWithException(
        FacialRecognitionExceptionCode.FACE_OVER_COUNT,
        "请保持只有一张面部在识别范围内",
      );
    }
  }

  /// 上传图像
  Future<void> uploadFile() async {
    try {
      final file = $.cachedFile!;
      final String fileType = file.path.split('.').last;
      if (['png', 'jpg'].contains(fileType) == false) {
        return abortWithException(
          FacialRecognitionExceptionCode.PTHOT_TYPE_ERROR,
          "上传的图像类型错误",
        );
      }
      $.imgCachedCallback?.call(file.path);
      final url = await rpc.storage.upload(
        file,
        fileType: fileType,
      );
      if (url == null || url.isEmpty) {
        return abortWithException(
          FacialRecognitionExceptionCode.PTHOT_UPLOAD_FAIL,
          "上传失败",
        );
      }
      $.result = url;
    } catch (e) {
      logger.e("FacialRecognitionCapturer._Worker upload file error.", e);
      abortWithException(
        FacialRecognitionExceptionCode.PTHOT_UPLOAD_FAIL,
        "上传人像失败",
      );
    } finally {
      $.imgCachedCallback?.call("");
    }
  }

  void abort() {
    isContinue = false;
    throw FacialRecognitionException(
      FacialRecognitionExceptionCode.ABORT,
      "中断",
    );
  }

  void abortWithException(int code, String message) {
    isContinue = false;
    throw FacialRecognitionException(code, message);
  }
}

class WorkVariables {
  camera.CameraController? cameraController;
  mlkit.FaceDetector? faceDetector;

  ValueChanged<String>? imgCachedCallback;

  camera.XFile? cachedFile;

  int faceNum = 0;
  String? result;
}
