import 'dart:convert';
import 'dart:typed_data';

import 'package:flutter_rust_bridge/flutter_rust_bridge.dart'
    show RustStreamSink;
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated_io.dart'
    show ExternalLibrary;

import 'src/rust/api.dart' hide authMode;
import 'src/rust/api.dart' as native;
import 'src/rust/frb_generated.dart';

export 'src/kusoft_frame.dart';
export 'src/rust/api.dart'
    show
        HandshakeInfo,
        CallParams,
        CallSignaling,
        KusoftSession,
        PushEvent,
        SessionOptions,
        UploadEvent,
        UploadEvent_Done,
        UploadEvent_Error,
        UploadEvent_Progress,
        WireLogEvent,
        connectCallSignaling,
        decodeVcp,
        setTrustMincifryCa,
        trustMincifryCa;

bool _initialized = false;

Future<void> initKusoft({String? libraryPath}) async {
  if (_initialized) return;
  await RustLib.init(
    externalLibrary:
        libraryPath == null ? null : ExternalLibrary.open(libraryPath),
  );
  _initialized = true;
}

SessionOptions _options({
  required String host,
  required int port,
  required String deviceId,
  required String instanceId,
  required String appVersion,
  required int buildNumber,
  required String deviceType,
  required String osVersion,
  required String timezone,
  required String screen,
  required String pushDeviceType,
  required String arch,
  required String locale,
  required String deviceName,
  required String deviceLocale,
  required int clientSessionId,
  required int pingIntervalSecs,
  required bool pingInteractive,
  required bool autoReconnect,
  required bool insecureTls,
  required String? proxy,
}) =>
    SessionOptions(
      host: host,
      port: port,
      deviceId: deviceId,
      instanceId: instanceId,
      appVersion: appVersion,
      buildNumber: buildNumber,
      deviceType: deviceType,
      osVersion: osVersion,
      timezone: timezone,
      screen: screen,
      pushDeviceType: pushDeviceType,
      arch: arch,
      locale: locale,
      deviceName: deviceName,
      deviceLocale: deviceLocale,
      clientSessionId: clientSessionId,
      pingIntervalSecs: BigInt.from(pingIntervalSecs),
      pingInteractive: pingInteractive,
      autoReconnect: autoReconnect,
      insecureTls: insecureTls,
      proxy: proxy,
    );

KusoftSession openSession({
  required String host,
  int port = 443,
  String deviceId = 'kusoft-dart',
  String instanceId = 'kusoft-dart',
  String appVersion = '26.20.2',
  int buildNumber = 6758,
  String deviceType = 'ANDROID',
  String osVersion = 'Android 14',
  String timezone = 'Europe/Moscow',
  String screen = '420dpi 420dpi 1080x2340',
  String pushDeviceType = 'GCM',
  String arch = 'arm64-v8a',
  String locale = 'ru',
  String deviceName = 'Dart',
  String deviceLocale = 'ru',
  int clientSessionId = 1700000000,
  int pingIntervalSecs = 30,
  bool pingInteractive = true,
  bool autoReconnect = true,
  bool insecureTls = false,
  String? proxy,
}) =>
    KusoftSession(
      options: _options(
        host: host,
        port: port,
        deviceId: deviceId,
        instanceId: instanceId,
        appVersion: appVersion,
        buildNumber: buildNumber,
        deviceType: deviceType,
        osVersion: osVersion,
        timezone: timezone,
        screen: screen,
        pushDeviceType: pushDeviceType,
        arch: arch,
        locale: locale,
        deviceName: deviceName,
        deviceLocale: deviceLocale,
        clientSessionId: clientSessionId,
        pingIntervalSecs: pingIntervalSecs,
        pingInteractive: pingInteractive,
        autoReconnect: autoReconnect,
        insecureTls: insecureTls,
        proxy: proxy,
      ),
    );

(KusoftSession, Stream<WireLogEvent>) openSessionWithWireLog({
  required String host,
  int port = 443,
  String deviceId = 'kusoft-dart',
  String instanceId = 'kusoft-dart',
  String appVersion = '26.20.2',
  int buildNumber = 6758,
  String deviceType = 'ANDROID',
  String osVersion = 'Android 14',
  String timezone = 'Europe/Moscow',
  String screen = '420dpi 420dpi 1080x2340',
  String pushDeviceType = 'GCM',
  String arch = 'arm64-v8a',
  String locale = 'ru',
  String deviceName = 'Dart',
  String deviceLocale = 'ru',
  int clientSessionId = 1700000000,
  int pingIntervalSecs = 30,
  bool pingInteractive = true,
  bool autoReconnect = true,
  bool insecureTls = false,
  String? proxy,
}) {
  final sink = RustStreamSink<WireLogEvent>();
  final session = KusoftSession(
    options: _options(
      host: host,
      port: port,
      deviceId: deviceId,
      instanceId: instanceId,
      appVersion: appVersion,
      buildNumber: buildNumber,
      deviceType: deviceType,
      osVersion: osVersion,
      timezone: timezone,
      screen: screen,
      pushDeviceType: pushDeviceType,
      arch: arch,
      locale: locale,
      deviceName: deviceName,
      deviceLocale: deviceLocale,
      clientSessionId: clientSessionId,
      pingIntervalSecs: pingIntervalSecs,
      pingInteractive: pingInteractive,
      autoReconnect: autoReconnect,
      insecureTls: insecureTls,
      proxy: proxy,
    ),
    wireLog: sink,
  );
  return (session, sink.stream);
}

extension KusoftRequestMap on KusoftSession {
  Future<Map<String, dynamic>> requestMap(
    int opcode,
    Map<String, dynamic> payload,
  ) async =>
      _asMap(
        await requestJson(
          opcode: opcode,
          jsonIn: jsonEncode(_escapeBinary(payload)),
        ),
      );

  Future<KusoftResponse> requestMapFull(
    int opcode,
    Map<String, dynamic> payload,
  ) async {
    final response = await requestFull(
      opcode: opcode,
      jsonIn: jsonEncode(_escapeBinary(payload)),
    );
    return KusoftResponse(
      cmd: response.cmd,
      opcode: response.opcode,
      payload: _asMap(response.payloadJson),
      errorMessage: response.errorMessage,
      errorKey: response.errorKey,
    );
  }

  Stream<(int, Map<String, dynamic>)> pushesMap() =>
      pushes().map((event) => (event.opcode, event.payloadMap));
}

class KusoftResponse {
  const KusoftResponse({
    required this.cmd,
    required this.opcode,
    required this.payload,
    this.errorMessage,
    this.errorKey,
  });

  final int cmd;
  final int opcode;
  final Map<String, dynamic> payload;
  final String? errorMessage;
  final String? errorKey;

  bool get isOk => cmd == 1;
  bool get isNotFound => cmd == 2;
  bool get isError => cmd == 3;
}

extension KusoftHandshakeMap on HandshakeInfo {
  Map<String, dynamic> get payloadMap => _asMap(payloadJson);
}

extension KusoftPushMap on PushEvent {
  Map<String, dynamic> get payloadMap => _asMap(payloadJson);
}

Uint8List authMode(
  int callsSeed,
  String deviceId, {
  List<int>? signature,
  List<int>? dex,
  List<int>? so,
}) =>
    native.authMode(
      signature: signature ?? _defaultSignatureDigest,
      dex: dex ?? _defaultDexDigest,
      so: so ?? _defaultSoDigest,
      callsSeed: callsSeed,
      deviceId: deviceId,
    );

Map<String, dynamic> _asMap(String source) {
  return decodeKusoftPayload(source);
}

Map<String, dynamic> decodeKusoftPayload(String source) {
  final decoded = _unescapePayload(jsonDecode(source));
  return decoded is Map ? Map<String, dynamic>.from(decoded) : {};
}

dynamic _unescapePayload(dynamic value) {
  if (value is Map) {
    if (value.length == 1 && value[r'$bin'] is String) {
      return base64.decode(value[r'$bin'] as String);
    }
    return value.map((key, item) {
      final candidate =
          key is String && key.startsWith(r'$int:') ? key.substring(5) : null;
      final normalizedKey = candidate != null && int.tryParse(candidate) != null
          ? candidate
          : key;
      return MapEntry(normalizedKey, _unescapePayload(item));
    });
  }
  if (value is List) return value.map(_unescapePayload).toList();
  return value;
}

dynamic _escapeBinary(dynamic value) {
  if (value is Uint8List) return {r'$bin': base64.encode(value)};
  if (value is Map) {
    return value.map(
      (key, item) => MapEntry(
        key is int ? '\$int:$key' : key,
        _escapeBinary(item),
      ),
    );
  }
  if (value is List) return value.map(_escapeBinary).toList();
  return value;
}

final Uint8List _defaultSignatureDigest = _hex(
  '1684414033eb263e2c615f8b7df5ed8793850a07656304997fbf07e9e21e1e93',
);
final Uint8List _defaultSoDigest = _hex(
  '90e2fb8745b17b42a10182f8d8ac590e3fca5b311e2ce2d5144fa2c18cb3090d',
);
final Uint8List _defaultDexDigest = _hex(
  '0a6265f6e5d8231b9cba641f8c40475e6f3baeb06ed41b804b9bf7307aa4214e',
);

Uint8List _hex(String source) {
  final output = Uint8List(source.length ~/ 2);
  for (var index = 0; index < output.length; index++) {
    output[index] = int.parse(
      source.substring(index * 2, index * 2 + 2),
      radix: 16,
    );
  }
  return output;
}
