import 'dart:async';
import 'dart:developer';
import 'package:taafee_mobile/core/errors/custom_exception.dart';

import 'package:socket_io_client/socket_io_client.dart';

import './event.dart';
import './extension.dart';

class SocketIO {
  // @attributes
  final String uri;
  Function()? onConnect;
  Function()? onDisconnect;
  Function()? onConnecting;
  Function()? onError;
  Map<String, dynamic> extraHeaders = {};

  // @constructors
  SocketIO({
    required this.uri,
    this.onConnect,
    this.onConnecting,
    this.onError,
    this.onDisconnect,
  });

  // @privates
  Socket? _socket; // suggest late instead of nullable

  bool _initialized = false;

  Status _status = Status.notConnected;

  final Map<String, dynamic> _socketOptions = {
    'transports': ['websocket'],
    'autoConnect': true,
  };

  // @getters
  bool get initialized => _initialized;

  Status get status => _status;

  // @methods
  Future<void> boot() async {
    init();
    await connect();
  }

  Future<void> restart() async {
    _assertInitialized();
    await disconnect();
    await boot();
  }

  void init() {
    _socketOptions["extraHeaders"] = extraHeaders;
    _socket = io(uri, _socketOptions);
    _initialized = true;
  }

  Future<void> connect() async {
    _assertInitialized();

    _status = Status.connecting;
    onConnecting?.call();

    await _socket!.connectAsync().timeout(const Duration(seconds: 10),
        onTimeout: () {
      onError?.call();
      throw GenericException(
        type: ExceptionType.connectionError,
        errorMessage: "You Have no Internet Connection",
      );
    });

    onConnect?.call();
    _status = Status.connected;
  }

  void setOnDisconnect(Function() trigger) {
    onDisconnect = trigger;
    _socket?.onDisconnect((_) => trigger());
  }

  void setOnConnect(Function() trigger) {
    onConnect = trigger;
    _socket?.onConnect((data) => trigger());
  }

  void setOnConnectionError(Function() trigger) {
    onError = trigger;
    _socket?.onerror((_) => trigger());
  }

  void setOnConnecting(Function() trigger) {
    onConnecting = trigger;
    _socket?.onerror((_) => trigger());
  }

  Future<void> disconnect() async {
    _assertInitialized();

    await _socket!.disconnectAsync();
    // suggest onDisconnect?.call();
    _status = Status.notConnected;
  }

  Future<dynamic> emit(Event event, {bool reciveDataOnError = false}) async {
    _assertInitialized();

    dynamic ack = {};

    try {
      ack = await _socket!.emitAsync(event.name, event.body);
      if (ack["status"] == "failed") {
        if (reciveDataOnError) {
          return ack;
        }
        throw badRequestException[
                ack["error"]["name"] ?? "INTERNAL_SERVER_ERROR"] ??
            GenericException(
              type: ExceptionType.internalServerException,
            );
      }
    } catch (e) {
      if (e is GenericException) {
        rethrow;
      }
      throw GenericException(
        type: ExceptionType.internalServerException,
      );
    }
    return ack["data"];
  }

  void listen(String eventName, Function(dynamic) handler) {
    // there is no need to check if initialized here, it is just registering a handler.
    _socket!.on(eventName, (data) {
      log('event:$eventName');
      handler(data);
    });
  }

  void setAuthentication(String token) {
    extraHeaders["authorization"] = token;
  }

  void _assertInitialized() {
    if (!initialized) {
      throw Exception("Object is not initialized, do call .init() method");
    }
  }

  void clearListeners() {
    _socket?.clearListeners();
  }
}

enum Status { connected, connecting, notConnected }
