import 'dart:async';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:alt_client/src/integrations/socket/socket_connection_controller.dart';
import 'package:alt_client/src/integrations/socket/socket_endpoint.dart';
import 'package:alt_client/src/integrations/socket/alt_socket_client.dart';
import 'package:alt_client/src/generated/alt/v1/common.pb.dart';

import 'alt_socket_client_test.dart'; // To use FakeWebSocket

class FakeAltSocketClient extends AltSocketClient {
  final Future<HelloResponse> Function()? helloMock;
  bool isClosed = false;
  final List<void Function(AltSocketClient)> _disconnectListeners = [];

  FakeAltSocketClient(super.ws, {this.helloMock});

  @override
  Future<HelloResponse> hello({Duration timeout = const Duration(seconds: 2)}) {
    if (helloMock != null) {
      return helloMock!();
    }
    return Future.value(
      HelloResponse()
        ..serverName = 'alt-server'
        ..serverVersion = '1.0.0'
        ..altProtocolVersion = 'alt.v1',
    );
  }

  @override
  void addDisconnectListener(void Function(AltSocketClient) fn) {
    _disconnectListeners.add(fn);
  }

  @override
  Future<void> close() async {
    isClosed = true;
    for (final listener in _disconnectListeners) {
      listener(this);
    }
  }

  void triggerDisconnect() {
    for (final listener in _disconnectListeners) {
      listener(this);
    }
  }
}

void main() {
  group('SocketConnectionController tests', () {
    late ProviderContainer container;
    late AltSocketEndpoint endpoint;
    late FakeWebSocket fakeWs;

    setUp(() {
      container = ProviderContainer();
      endpoint = const AltSocketEndpoint(
        host: '127.0.0.1',
        port: 9000,
        path: '/test-socket',
      );
      fakeWs = FakeWebSocket();
    });

    tearDown(() {
      container.dispose();
    });

    test('initial state is disconnected', () {
      final state = container.read(socketConnectionControllerProvider);
      expect(state.status, equals(SocketConnectionStatus.disconnected));
      expect(state.endpoint, isNull);
      expect(state.reasonCode, isNull);
      expect(state.lastError, isNull);
    });

    test('successful connection and handshake', () async {
      final controller = container.read(
        socketConnectionControllerProvider.notifier,
      );
      late FakeAltSocketClient client;

      controller.connectFactory = (ep) async {
        client = FakeAltSocketClient(fakeWs);
        return client;
      };

      final connectFuture = controller.connect(endpoint);

      // Verify connecting state
      expect(
        container.read(socketConnectionControllerProvider).status,
        equals(SocketConnectionStatus.connecting),
      );
      expect(
        container.read(socketConnectionControllerProvider).endpoint,
        equals(endpoint),
      );

      await connectFuture;

      // Verify connected state
      final state = container.read(socketConnectionControllerProvider);
      expect(state.status, equals(SocketConnectionStatus.connected));
      expect(state.endpoint, equals(endpoint));
      expect(state.reasonCode, isNull);
      expect(state.lastError, isNull);
      expect(controller.client, equals(client));
    });

    test('connect throws standard error', () async {
      final controller = container.read(
        socketConnectionControllerProvider.notifier,
      );
      final error = Exception('Failed to connect');

      controller.connectFactory = (ep) async {
        throw error;
      };

      await expectLater(controller.connect(endpoint), throwsA(equals(error)));

      final state = container.read(socketConnectionControllerProvider);
      expect(state.status, equals(SocketConnectionStatus.error));
      expect(state.endpoint, equals(endpoint));
      expect(state.reasonCode, equals('connect_failed'));
      expect(state.lastError, equals(error));
    });

    test('connect throws TimeoutException', () async {
      final controller = container.read(
        socketConnectionControllerProvider.notifier,
      );
      final error = TimeoutException('Connect timeout');

      controller.connectFactory = (ep) async {
        throw error;
      };

      await expectLater(controller.connect(endpoint), throwsA(equals(error)));

      final state = container.read(socketConnectionControllerProvider);
      expect(state.status, equals(SocketConnectionStatus.timeout));
      expect(state.endpoint, equals(endpoint));
      expect(state.reasonCode, equals('connect_timeout'));
      expect(state.lastError, equals(error));
    });

    test('handshake hello throws standard error', () async {
      final controller = container.read(
        socketConnectionControllerProvider.notifier,
      );
      final error = Exception('Hello failed');
      late FakeAltSocketClient client;

      controller.connectFactory = (ep) async {
        client = FakeAltSocketClient(
          fakeWs,
          helloMock: () async {
            throw error;
          },
        );
        return client;
      };

      await expectLater(controller.connect(endpoint), throwsA(equals(error)));

      final state = container.read(socketConnectionControllerProvider);
      expect(state.status, equals(SocketConnectionStatus.error));
      expect(state.endpoint, equals(endpoint));
      expect(state.reasonCode, equals('hello_failed'));
      expect(state.lastError, equals(error));
    });

    test('handshake hello throws TimeoutException', () async {
      final controller = container.read(
        socketConnectionControllerProvider.notifier,
      );
      final error = TimeoutException('Hello timeout');
      late FakeAltSocketClient client;

      controller.connectFactory = (ep) async {
        client = FakeAltSocketClient(
          fakeWs,
          helloMock: () async {
            throw error;
          },
        );
        return client;
      };

      await expectLater(controller.connect(endpoint), throwsA(equals(error)));

      final state = container.read(socketConnectionControllerProvider);
      expect(state.status, equals(SocketConnectionStatus.timeout));
      expect(state.endpoint, equals(endpoint));
      expect(state.reasonCode, equals('hello_timeout'));
      expect(state.lastError, equals(error));
    });

    test('explicit disconnect', () async {
      final controller = container.read(
        socketConnectionControllerProvider.notifier,
      );
      late FakeAltSocketClient client;

      controller.connectFactory = (ep) async {
        client = FakeAltSocketClient(fakeWs);
        return client;
      };

      await controller.connect(endpoint);
      expect(
        container.read(socketConnectionControllerProvider).status,
        equals(SocketConnectionStatus.connected),
      );

      await controller.disconnect();

      final state = container.read(socketConnectionControllerProvider);
      expect(state.status, equals(SocketConnectionStatus.disconnected));
      expect(state.endpoint, equals(endpoint));
      expect(state.reasonCode, equals('explicit_disconnect'));
      expect(controller.client, isNull);
      expect(client.isClosed, isTrue);
    });

    test('disconnect listener triggers state update', () async {
      final controller = container.read(
        socketConnectionControllerProvider.notifier,
      );
      late FakeAltSocketClient client;

      controller.connectFactory = (ep) async {
        client = FakeAltSocketClient(fakeWs);
        return client;
      };

      await controller.connect(endpoint);
      expect(
        container.read(socketConnectionControllerProvider).status,
        equals(SocketConnectionStatus.connected),
      );

      client.triggerDisconnect();

      final state = container.read(socketConnectionControllerProvider);
      expect(state.status, equals(SocketConnectionStatus.disconnected));
      expect(state.endpoint, equals(endpoint));
      expect(state.reasonCode, equals('socket_closed'));
    });
  });
}
