// ignore_for_file: avoid_init_to_null, prefer_final_fields

import 'dart:io';
import 'dart:async';
import 'dart:collection';
import 'dart:typed_data';

import 'package:protobuf/protobuf.dart';
import 'base_client.dart';
import 'inbound_gateway_io.dart';
import 'packets/message_common.pb.dart';
import 'transport.dart';

abstract class WsProtobufClient extends BaseClient<WsProtobufClient> {
  final WebSocket _ws;
  late final _WebSocketTransport _transport;

  /// Plain WebSocket connection.
  static Future<WebSocket> connect(String host, int port,
          {String path = '/'}) =>
      WebSocket.connect('ws://$host:$port$path');

  /// Secure WebSocket (WSS) connection.
  static Future<WebSocket> connectSecure(String host, int port,
      {String path = '/',
      SecurityContext? context,
      bool Function(X509Certificate certificate)? onBadCertificate}) {
    final client =
        HttpClient(context: context ?? SecurityContext.defaultContext);
    if (onBadCertificate != null) {
      client.badCertificateCallback = (certificate, _, __) {
        return onBadCertificate(certificate);
      };
    }
    return WebSocket.connect('wss://$host:$port$path', customClient: client);
  }

  StreamSubscription? _subscription;
  final Queue<dynamic> _pending = Queue<dynamic>();
  bool _draining = false;

  WsProtobufClient(this._ws, int heartbeatIntervalTime, int heartbeatWaitTime,
      Map<String, GeneratedMessage Function(List<int>)> parserMap) {
    initSelf(this);
    initHeartbeat(heartbeatIntervalTime, heartbeatWaitTime);
    _transport = _WebSocketTransport(_ws);
    isAlive = true;
    parserMap.addAll({HeartBeat.getDefault().info_.qualifiedMessageName: HeartBeat.fromBuffer});
    super.initialize(parserMap, transport: _transport);
    // TCP 경로와 동일한 gateway-backed receive coordinator를 사용한다. start()는
    // _ready completer를 동기 구간에서 설정하므로 spawn 전 첫 프레임도 drop되지 않는다.
    final gateway = IsolateInboundGateway();
    attachInboundGateway(gateway);
    unawaited(gateway.start());
    _subscription = _ws.listen(_onMessage, onError: onError, onDone: close);
    addListener(onHeartBeat);
    sendHeartBeat();
  }

  void onError(dynamic e) {}

  // burst 상황에서 pause()는 동기적으로 적용되지 않아 이미 전달된 frame이 남는다.
  // 도착한 frame을 drop하지 않고 queue에 넣은 뒤, 단일 drain loop가 순서대로
  // dispatch한다. pause/resume은 source backpressure 용도로 drain 단위에서만 건다.
  void _onMessage(dynamic data) {
    _pending.add(data);
    _drain();
  }

  Future<void> _drain() async {
    if (_draining) return;
    _draining = true;
    _subscription?.pause();
    try {
      while (_pending.isNotEmpty) {
        await _dispatch(_pending.removeFirst());
      }
    } finally {
      _draining = false;
      _subscription?.resume();
    }
  }

  Future<void> _dispatch(dynamic data) async {
    final bytes = data is List<int> ? data : (data as Uint8List).toList();
    await onReceivedFrame(bytes);
    sendHeartBeat();
  }

  @override
  Future<void> closeTransport() async {
    await _transport.close();
  }

  @override
  bool get isSourcePaused => _subscription?.isPaused ?? false;
}

class _WebSocketTransport implements Transport {
  final WebSocket _ws;

  _WebSocketTransport(this._ws);

  @override
  Future<void> writePacket(PacketBase base) async {
    _ws.add(base.writeToBuffer());
  }

  @override
  Future<void> close() async {
    try {
      await _ws.close();
    } catch (_) {
      // already closed by peer
    }
  }
}
