import 'dart:async';

import 'proto_socket_endpoint_config.dart';
import 'proto_socket_envelope.dart';

enum ProtoSocketConnectionState { disconnected, connecting, connected, failed }

abstract interface class ProtoSocketTransport {
  Future<ProtoSocketEnvelope> sendRequest(
    ProtoSocketEnvelope envelope, {
    Duration timeout = const Duration(seconds: 30),
  });
  Stream<ProtoSocketEnvelope> get events;
  Future<void> close();
}

/// Pluggable connector. Production wires a real proto-socket client; tests
/// inject a fake to drive lifecycle transitions deterministically without
/// opening network sockets.
abstract interface class ProtoSocketConnector {
  Future<ProtoSocketTransport> connect(ProtoSocketEndpointConfig config);
  Future<void> disconnect();
}

/// App-facing lifecycle facade for proto-socket integration.
///
/// Owns the connection state machine and exposes a stream for observers.
/// Does NOT auto-connect on construction — bootstrap creates the facade with
/// a config and an explicit `connect()` call (or skips it entirely when the
/// integration is disabled).
class ProtoSocketLifecycle {
  final ProtoSocketConnector _connector;
  final StreamController<ProtoSocketConnectionState> _stateController =
      StreamController<ProtoSocketConnectionState>.broadcast();

  ProtoSocketConnectionState _state = ProtoSocketConnectionState.disconnected;
  Object? _lastError;
  ProtoSocketTransport? _transport;

  ProtoSocketLifecycle({required ProtoSocketConnector connector})
    : _connector = connector;

  ProtoSocketConnectionState get state => _state;
  Stream<ProtoSocketConnectionState> get stateStream => _stateController.stream;
  Object? get lastError => _lastError;
  ProtoSocketTransport? get transport => _transport;

  Future<void> connect(ProtoSocketEndpointConfig config) async {
    if (_state == ProtoSocketConnectionState.connecting ||
        _state == ProtoSocketConnectionState.connected) {
      return;
    }
    _emit(ProtoSocketConnectionState.connecting);
    try {
      final transport = await _connector.connect(config);
      _transport = transport;
      _lastError = null;
      _emit(ProtoSocketConnectionState.connected);
    } catch (e) {
      _transport = null;
      _lastError = e;
      _emit(ProtoSocketConnectionState.failed);
      rethrow;
    }
  }

  Future<void> disconnect() async {
    if (_state == ProtoSocketConnectionState.disconnected) return;
    await _connector.disconnect();
    _transport = null;
    _emit(ProtoSocketConnectionState.disconnected);
  }

  Future<void> dispose() async {
    await _stateController.close();
  }

  void _emit(ProtoSocketConnectionState next) {
    _state = next;
    if (!_stateController.isClosed) {
      _stateController.add(next);
    }
  }
}

/// Test/fallback connector for disabled or fake scenarios.
class NoopProtoSocketConnector implements ProtoSocketConnector {
  @override
  Future<ProtoSocketTransport> connect(
    ProtoSocketEndpointConfig config,
  ) async => _NoopProtoSocketTransport();

  @override
  Future<void> disconnect() async {}
}

class _NoopProtoSocketTransport implements ProtoSocketTransport {
  final StreamController<ProtoSocketEnvelope> _events =
      StreamController<ProtoSocketEnvelope>.broadcast();

  @override
  Stream<ProtoSocketEnvelope> get events => _events.stream;

  @override
  Future<ProtoSocketEnvelope> sendRequest(
    ProtoSocketEnvelope envelope, {
    Duration timeout = const Duration(seconds: 30),
  }) {
    return Future.error(StateError('proto-socket transport is not connected'));
  }

  @override
  Future<void> close() => _events.close();
}
