// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

@TestOn('vm')
@Timeout(Duration(minutes: 2))
library;

import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:test/test.dart';
import 'package:test_common/test_sdk_configuration.dart';
import 'package:vm_service/vm_service.dart';

import 'fixtures/context.dart';
import 'fixtures/project.dart';
import 'fixtures/utilities.dart';

void main() {
  final provider = TestSdkConfigurationProvider();
  tearDownAll(provider.dispose);

  final context = TestContext(TestProject.test, provider);

  setUpAll(() async {
    // Disable DDS as we're testing DWDS behavior.
    await context.setUp(
      debugSettings: TestDebugSettings.noDevTools().copyWith(spawnDds: false),
    );
  });

  tearDownAll(() async {
    await context.tearDown();
  });

  test('Refuses connections without the auth token', () async {
    expect(
      WebSocket.connect('ws://localhost:${context.debugConnection.port}/ws'),
      throwsA(isA<WebSocketException>()),
    );
  });

  test('Accepts connections with the auth token', () async {
    expect(
      WebSocket.connect(
        '${context.debugConnection.uri}/ws',
      ).then((ws) => ws.close()),
      completes,
    );
  });

  test('Refuses additional connections when in single client mode', () async {
    final ddsWs = await WebSocket.connect('${context.debugConnection.uri}/ws');
    final completer = Completer<void>();
    ddsWs.listen((event) {
      final response = json.decode(event as String);
      expect(response['id'], '0');
      expect(response.containsKey('result'), isTrue);
      final result = response['result'] as Map<String, dynamic>;
      expect(result['type'], 'Success');
      completer.complete();
    });

    const yieldControlToDDS = <String, dynamic>{
      'jsonrpc': '2.0',
      'id': '0',
      'method': '_yieldControlToDDS',
      'params': {'uri': 'http://localhost:123'},
    };
    ddsWs.add(json.encode(yieldControlToDDS));
    await completer.future;

    // While DDS is connected, expect additional connections to fail.
    await expectLater(
      WebSocket.connect('${context.debugConnection.uri}/ws'),
      throwsA(isA<WebSocketException>()),
    );

    // However, once DDS is disconnected, additional clients can connect again.
    await ddsWs.close();
    expect(
      WebSocket.connect(
        '${context.debugConnection.uri}/ws',
      ).then((ws) => ws.close()),
      completes,
    );
  });

  test('Refuses to yield to dwds if existing clients found', () async {
    final ddsWs = await WebSocket.connect('${context.debugConnection.uri}/ws');

    // Connect to vm service.
    final ws = await WebSocket.connect('${context.debugConnection.uri}/ws');

    final completer = Completer<Map<String, dynamic>>();
    ddsWs.listen((event) {
      completer.complete(json.decode(event as String));
    });

    const yieldControlToDDS = <String, dynamic>{
      'jsonrpc': '2.0',
      'id': '0',
      'method': '_yieldControlToDDS',
      'params': {'uri': 'http://localhost:123'},
    };

    // DDS should fail to start with existing vm clients.
    ddsWs.add(json.encode(yieldControlToDDS));

    final response = await completer.future;
    expect(response['id'], '0');
    expect(response.containsKey('error'), isTrue);

    final result = response['error'] as Map<String, dynamic>;
    expect(result['code'], RPCErrorKind.kFeatureDisabled.code);
    expect(
      result['message'],
      'Existing VM service clients prevent DDS from taking control.',
    );

    await ddsWs.close();
    await ws.close();
  });
}
