// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';

import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/commands/daemon.dart';
import 'package:test/fake.dart';

import '../../src/common.dart';

void main() {
  testWithoutContext('binds on ipv4 normally', () async {
    final FakeServerSocket socket = FakeServerSocket();
    final BufferLogger logger = BufferLogger.test();

    int bindCalledTimes = 0;
    final List<Object?> bindAddresses = <Object?>[];
    final List<int> bindPorts = <int>[];

    final DaemonServer server = DaemonServer(
      port: 123,
      logger: logger,
      bind: (Object? address, int port) async {
        bindCalledTimes++;
        bindAddresses.add(address);
        bindPorts.add(port);
        return socket;
      },
    );
    await server.run();
    expect(bindCalledTimes, 1);
    expect(bindAddresses, <Object?>[InternetAddress.loopbackIPv4]);
    expect(bindPorts, <int>[123]);
  });

  testWithoutContext('binds on ipv6 if ipv4 failed normally', () async {
    final FakeServerSocket socket = FakeServerSocket();
    final BufferLogger logger = BufferLogger.test();

    int bindCalledTimes = 0;
    final List<Object?> bindAddresses = <Object?>[];
    final List<int> bindPorts = <int>[];

    final DaemonServer server = DaemonServer(
      port: 123,
      logger: logger,
      bind: (Object? address, int port) async {
        bindCalledTimes++;
        bindAddresses.add(address);
        bindPorts.add(port);
        if (address == InternetAddress.loopbackIPv4) {
          throw const SocketException('fail');
        }
        return socket;
      },
    );
    await server.run();
    expect(bindCalledTimes, 2);
    expect(bindAddresses, <Object?>[InternetAddress.loopbackIPv4, InternetAddress.loopbackIPv6]);
    expect(bindPorts, <int>[123, 123]);
  });
}

class FakeServerSocket extends Fake implements ServerSocket {
  FakeServerSocket();

  @override
  int get port => 1;

  bool closeCalled = false;
  final StreamController<Socket> controller = StreamController<Socket>();

  @override
  StreamSubscription<Socket> listen(
    void Function(Socket event)? onData, {
    Function? onError,
    void Function()? onDone,
    bool? cancelOnError,
  }) {
    // Close the controller immediately for testing purpose.
    scheduleMicrotask(() {
      controller.close();
    });
    return controller.stream.listen(onData,
        onError: onError, onDone: onDone, cancelOnError: cancelOnError);
  }

  @override
  Future<ServerSocket> close() async {
    closeCalled = true;
    return this;
  }
}
