import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:platform_foundation/core.dart' hide Header;
import 'package:platform_foundation/http.dart';
import 'package:http2/transport.dart';
import 'package:platform_testing/http.dart';
import 'http2_request_context.dart';
import 'http2_response_context.dart';
import 'package:uuid/uuid.dart';

/// Boots a shared server instance. Use this if launching multiple isolates.
Future<SecureServerSocket> startSharedHttp2(
    address, int port, SecurityContext ctx) {
  return SecureServerSocket.bind(address, port, ctx, shared: true);
}

/// Adapts `package:http2`'s [ServerTransportConnection] to serve Protevus.
class PlatformHttp2 extends Driver<Socket, ServerTransportStream,
    SecureServerSocket, Http2RequestContext, Http2ResponseContext> {
  final ServerSettings? settings;
  late PlatformHttp _http;
  final StreamController<HttpRequest> _onHttp1 = StreamController();
  final Map<String, MockHttpSession> _sessions = {};
  final Uuid _uuid = Uuid();
  _ProtevusHttp2ServerSocket? _artificial;

  SecureServerSocket? get socket => _artificial;

  PlatformHttp2._(
      Application app,
      Future<SecureServerSocket> Function(dynamic, int) serverGenerator,
      bool useZone,
      bool allowHttp1,
      this.settings)
      : super(
          app,
          serverGenerator,
          useZone: useZone,
        ) {
    if (allowHttp1) {
      _http = PlatformHttp(app, useZone: useZone);
      onHttp1.listen(_http.handleRequest);
    }
  }

  factory PlatformHttp2(Application app, SecurityContext securityContext,
      {bool useZone = true,
      bool allowHttp1 = false,
      ServerSettings? settings}) {
    return PlatformHttp2.custom(app, securityContext, SecureServerSocket.bind,
        allowHttp1: allowHttp1, settings: settings);
  }

  factory PlatformHttp2.custom(
      Application app,
      SecurityContext ctx,
      Future<SecureServerSocket> Function(
              InternetAddress? address, int port, SecurityContext ctx)
          serverGenerator,
      {bool useZone = true,
      bool allowHttp1 = false,
      ServerSettings? settings}) {
    return PlatformHttp2._(app, (address, port) {
      var addr = address is InternetAddress
          ? address
          : InternetAddress(address.toString());
      return Future.sync(() => serverGenerator(addr, port, ctx));
    }, useZone, allowHttp1, settings);
  }

  /// Fires when an HTTP/1.x request is received.
  Stream<HttpRequest> get onHttp1 => _onHttp1.stream;

  @override
  Future<SecureServerSocket> generateServer([address, int? port]) async {
    var s = await serverGenerator(address ?? '127.0.0.1', port ?? 0);
    return _artificial = _ProtevusHttp2ServerSocket(s, this);
  }

  @override
  Future<void> close() async {
    await _artificial?.close();
    await _http.close();
    return await super.close();
  }

  @override
  void addCookies(ServerTransportStream response, Iterable<Cookie> cookies) {
    var headers =
        cookies.map((cookie) => Header.ascii('set-cookie', cookie.toString()));
    response.sendHeaders(headers.toList());
  }

  @override
  Future closeResponse(ServerTransportStream response) {
    response.terminate();
    return Future.value();
  }

  @override
  Future<Http2RequestContext> createRequestContext(
      Socket request, ServerTransportStream response) {
    return Http2RequestContext.from(response, request, app, _sessions, _uuid);
  }

  @override
  Future<Http2ResponseContext> createResponseContext(
      Socket request, ServerTransportStream response,
      [Http2RequestContext? correspondingRequest]) async {
    return Http2ResponseContext(app, response, correspondingRequest)
      ..encoders.addAll(app.encoders);
  }

  @override
  Stream<ServerTransportStream> createResponseStreamFromRawRequest(
      Socket request) {
    var connection =
        ServerTransportConnection.viaSocket(request, settings: settings);
    return connection.incomingStreams;
  }

  @override
  void setChunkedEncoding(ServerTransportStream response, bool value) {
    // Do nothing in HTTP/2
  }

  @override
  void setContentLength(ServerTransportStream response, int length) {
    setHeader(response, 'content-length', length.toString());
  }

  @override
  void setHeader(ServerTransportStream response, String key, String? value) {
    response.sendHeaders([Header.ascii(key, value!)]);
  }

  @override
  void setStatusCode(ServerTransportStream response, int value) {
    response.sendHeaders([Header.ascii(':status', value.toString())]);
  }

  @override
  Uri get uri => Uri(
      scheme: 'https',
      host: server?.address.address,
      port: server?.port != 443 ? server?.port : null);

  @override
  void writeStringToResponse(ServerTransportStream response, String value) {
    writeToResponse(response, utf8.encode(value));
  }

  @override
  void writeToResponse(ServerTransportStream response, List<int> data) {
    response.sendData(data);
  }
}

class _FakeServerSocket extends Stream<Socket> implements ServerSocket {
  final _ProtevusHttp2ServerSocket protevus;
  final _ctrl = StreamController<Socket>();

  _FakeServerSocket(this.protevus);

  @override
  InternetAddress get address => protevus.address;

  @override
  Future<ServerSocket> close() async {
    await (_ctrl.close());
    return this;
  }

  @override
  int get port => protevus.port;

  @override
  StreamSubscription<Socket> listen(void Function(Socket event)? onData,
      {Function? onError, void Function()? onDone, bool? cancelOnError}) {
    return _ctrl.stream.listen(onData,
        cancelOnError: cancelOnError, onError: onError, onDone: onDone);
  }
}

class _ProtevusHttp2ServerSocket extends Stream<SecureSocket>
    implements SecureServerSocket {
  final SecureServerSocket socket;
  final PlatformHttp2 driver;
  final _ctrl = StreamController<SecureSocket>();
  late _FakeServerSocket _fake;
  StreamSubscription? _sub;

  _ProtevusHttp2ServerSocket(this.socket, this.driver) {
    _fake = _FakeServerSocket(this);
    HttpServer.listenOn(_fake).pipe(driver._onHttp1);
    _sub = socket.listen(
      (socket) {
        if (socket.selectedProtocol == null ||
            socket.selectedProtocol == 'http/1.0' ||
            socket.selectedProtocol == 'http/1.1') {
          _fake._ctrl.add(socket);
        } else if (socket.selectedProtocol == 'h2' ||
            socket.selectedProtocol == 'h2-14') {
          _ctrl.add(socket);
        } else {
          socket.destroy();
          throw Exception(
              'PlatformHttp2 does not support ${socket.selectedProtocol} as an ALPN protocol.');
        }
      },
      onDone: _ctrl.close,
      onError: (e, st) {
        driver.app.logger.warning(
            'HTTP/2 incoming connection failure: ', e, st as StackTrace);
      },
    );
  }

  @override
  InternetAddress get address => socket.address;

  @override
  int get port => socket.port;

  @override
  Future<SecureServerSocket> close() {
    _sub?.cancel();
    _fake.close();
    _ctrl.close();
    return socket.close();
  }

  @override
  StreamSubscription<SecureSocket> listen(
      void Function(SecureSocket event)? onData,
      {Function? onError,
      void Function()? onDone,
      bool? cancelOnError}) {
    return _ctrl.stream.listen(onData,
        cancelOnError: cancelOnError, onError: onError, onDone: onDone);
  }
}
