// 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 'dart:io' as io;

import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:test/fake.dart';

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

void main() {
  testWithoutContext('IOOverrides can inject a memory file system', () async {
    final memoryFileSystem = MemoryFileSystem.test();
    final flutterIOOverrides = FlutterIOOverrides(fileSystem: memoryFileSystem);
    await io.IOOverrides.runWithIOOverrides(() async {
      // statics delegate correctly.
      expect(io.FileSystemEntity.isWatchSupported, memoryFileSystem.isWatchSupported);
      expect(io.Directory.systemTemp.path, memoryFileSystem.systemTempDirectory.path);

      // can create and write to files/directories sync.
      final file = io.File('abc');
      file.writeAsStringSync('def');
      final directory = io.Directory('foobar');
      directory.createSync();

      expect(memoryFileSystem.file('abc').existsSync(), true);
      expect(memoryFileSystem.file('abc').readAsStringSync(), 'def');
      expect(memoryFileSystem.directory('foobar').existsSync(), true);

      // can create and write to files/directories async.
      final fileB = io.File('xyz');
      await fileB.writeAsString('def');
      final directoryB = io.Directory('barfoo');
      await directoryB.create();

      expect(memoryFileSystem.file('xyz').existsSync(), true);
      expect(memoryFileSystem.file('xyz').readAsStringSync(), 'def');
      expect(memoryFileSystem.directory('barfoo').existsSync(), true);

      // Links
      final linkA = io.Link('hhh');
      final linkB = io.Link('ggg');
      io.File('jjj').createSync();
      io.File('lll').createSync();
      await linkA.create('jjj');
      linkB.createSync('lll');

      expect(
        await memoryFileSystem.link('hhh').resolveSymbolicLinks(),
        await linkA.resolveSymbolicLinks(),
      );
      expect(
        memoryFileSystem.link('ggg').resolveSymbolicLinksSync(),
        linkB.resolveSymbolicLinksSync(),
      );
    }, flutterIOOverrides);
  });

  testWithoutContext('ProcessSignal signals are properly delegated', () async {
    final signal = FakeProcessSignal();
    final signalUnderTest = ProcessSignal(signal);

    signal.controller.add(signal);

    expect(signalUnderTest, await signalUnderTest.watch().first);
  });

  testWithoutContext('ProcessSignal toString() works', () async {
    expect(io.ProcessSignal.sigint.toString(), ProcessSignal.sigint.toString());
  });

  testWithoutContext('exit throws a StateError if called without being overridden', () {
    expect(() => exit(0), throwsAssertionError);
  });

  testWithoutContext('exit does not throw a StateError if overridden', () {
    try {
      setExitFunctionForTests((int value) {});

      expect(() => exit(0), returnsNormally);
    } finally {
      restoreExitFunction();
    }
  });

  testWithoutContext('test_api defines the Declarer in a known place', () {
    expect(Zone.current[#test.declarer], isNotNull);
  });

  testWithoutContext('listNetworkInterfaces() uses overrides', () async {
    setNetworkInterfaceLister(
      ({bool? includeLoopback, bool? includeLinkLocal, InternetAddressType? type}) async =>
          <NetworkInterface>[],
    );

    expect(await listNetworkInterfaces(), isEmpty);

    resetNetworkInterfaceLister();
  });

  testWithoutContext('Does not listen to Posix process signals on windows', () async {
    final windows = FakePlatform(operatingSystem: 'windows');
    final linux = FakePlatform();
    final fakeSignalA = FakeProcessSignal();
    final fakeSignalB = FakeProcessSignal();
    fakeSignalA.controller.add(fakeSignalA);
    fakeSignalB.controller.add(fakeSignalB);

    expect(await PosixProcessSignal(fakeSignalA, platform: windows).watch().isEmpty, true);
    expect(await PosixProcessSignal(fakeSignalB, platform: linux).watch().first, isNotNull);
  });
}

class FakeProcessSignal extends Fake implements io.ProcessSignal {
  final controller = StreamController<io.ProcessSignal>();

  @override
  Stream<io.ProcessSignal> watch() => controller.stream;
}
