import 'dart:async';
import 'dart:typed_data';
import 'package:common_pub/model/position.dart';
import 'package:common_pub/model/game_map.dart';
import 'package:get/get.dart';
import 'package:grpc/grpc.dart';
import '../logger.dart';
import 'app.dart';
import '../global_var.dart';
import '../pb.dart' as pb;
export '../pb.dart';
typedef SmsType = pb.SmsType;


class ApiService extends GetxService{
  static ApiService get to => Get.find();
  AppService get app => AppService.to;
  ClientChannel? channel;
  String get _appVersion => app.appVersion;

  Future<ApiService> init()async{
    channel = _newChannel();
    await syncTime();
    return this;
  }

  String? get token {
    final out = app.userProfile.token.val;
    if (out.isEmpty) {
      return null;
    }
    return out;
  }
  set token(String? v) {
    app.userProfile.token.val = v ?? '';
  }

  ClientChannel _newChannel(){
    return ClientChannel(
      GlobalVar.apiHost,
      port: GlobalVar.apiPort,
      options: const ChannelOptions(credentials:
      ChannelCredentials.secure()
      // ChannelCredentials.insecure()
      ),
    );
  }

  pb.ApiToAppClient _newStub({Duration? timeout, ClientChannel? channel}){
    if (this.channel == null) {
      throw Exception('$runtimeType 未初始化');
    }
    final metadata = <String, String>{
      'source': "${pb.LoginSource.ToApp.value}"
    };
    metadata['version'] = _appVersion;
    if (token != null) {
      metadata['token'] = token!;
    }
    // debug("token: $token");

    return pb.ApiToAppClient(channel??this.channel!,
        options: CallOptions(
          metadata: metadata,
          timeout: timeout,
        ));
  }

  pb.ApiToAppClient get stub {
    return _newStub(timeout: 10.seconds);
  }

  // 获取系统时间
  Future<DateTime> serverTime() async {
    final begin = DateTime.now();
    final r = await stub.toGetServerTime(pb.DefaultRequest());
    final cost = DateTime.now().difference(begin);
    final serverNow = DateTime.fromMillisecondsSinceEpoch(
        r.millisecondStamp.toInt(),
        isUtc: true)
        .toLocal();
    return serverNow.add(cost);
  }

  Future<void> syncTime()async{
    try {
      final serverNow = await serverTime();
      app.correctByServerNow(serverNow);
      info('服务器时间：${app.now}');
    } catch(e){
      warn("获取服务器时间失败: ", e);
    }
  }


  // 获取短信验证码
  Future<void> authSendCodeToPhone(String phone, SmsType smsType)async{
    info('authSendCodeToPhone [$phone]');
    await stub.toSendCodeToPhoneV2(pb.ToSendCodeToPhoneRequestV2()
      ..phone= phone
      ..smsType= smsType
    );
  }

  // 场控端_登录
  Future<void> signIn(String userCode, String password, String ip) async {
    final r = await stub.toSignInV2(pb.ToSignInRequestV2()
      ..userCode = userCode
      ..password = password
      ..ip = ip
    );
    token = r.token;

    debug('sign in success: $token');
  }

  // 场控端_登出
  void signOut(){
    stub.toSignOutV2(pb.DefaultRequest());
    token = null;
  }

  Future<Duration> getSmsSendLeftTime(String phone)async{
    final r = await stub.toGetSmsSendLeftTimeV2(pb.GetSmsSendLeftTimeRequest()..phone= phone);
    info('getSmsSendLeftTime: $phone - ${r.second}s');
    return r.second.seconds;
  }
  Future<BinReader> getBinReaderByMd5(Uint8List md5) async {
    final stream = stub.toGetBinaryByMd5(pb.ToGetBinaryByMd5Request()..md5=md5);
    final controller = StreamController<List<int>>();
    controller.onCancel = (){
      stream.cancel();
    };

    Future<void> rcv()async{
      try{
        await for(final one in stream){
          controller.add(one.data);
        }
      }finally{
        controller.close();
        stream.cancel();
      }
    }
    rcv();
    stream.headers.then((value) => debug(value));
    final headers = await stream.headers;
    final lenStr = headers['all-length']!;
    final length = int.parse(lenStr);
    final nonce = headers['nonce']!;
    final ext = headers['ext']!;

    return BinReader(
        data: controller.stream,
        length: length,
        ext: ext,
        nonce: nonce
    );
  }

}


extension NetImageExt on pb.NetImage{
  GameMap toGameMap(){
    final md5Data = Uint8List.fromList(md5);

    return GameMap()
      ..url = url
      ..md5 = md5Data
      ..readerBuilder = ()=> ApiService.to.getBinReaderByMd5(md5Data);
  }
}

extension DateTimeExt on pb.Timestamp{
  DateTime toModel(){
    return toDateTime(toLocal: true);
  }
}
extension PositionExt on pb.GameGpsInfo{
  Position toModel(){
    return Position(latitude: latitude, longitude: longitude)
      ..altitude=altitude;
  }
}