// 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 'package:file/file.dart';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/terminal.dart';
import 'package:flutter_tools/src/cmake_project.dart';
import 'package:flutter_tools/src/windows/migrations/show_window_migration.dart';
import 'package:test/fake.dart';

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

void main () {
  group('Windows Flutter show window migration', () {
    late MemoryFileSystem memoryFileSystem;
    late BufferLogger testLogger;
    late FakeWindowsProject mockProject;
    late File flutterWindowFile;

    setUp(() {
      memoryFileSystem = MemoryFileSystem.test();
      flutterWindowFile = memoryFileSystem.file('flutter_window.cpp');

      testLogger = BufferLogger(
        terminal: Terminal.test(),
        outputPreferences: OutputPreferences.test(),
      );

      mockProject = FakeWindowsProject(flutterWindowFile);
    });

    testWithoutContext('skipped if Flutter window file is missing', () {
      final ShowWindowMigration migration = ShowWindowMigration(
        mockProject,
        testLogger,
      );
      migration.migrate();

      expect(flutterWindowFile.existsSync(), isFalse);

      expect(
        testLogger.traceText,
        contains(
          'windows/runner/flutter_window.cpp file not found, '
          'skipping show window migration'
        ),
      );
      expect(testLogger.statusText, isEmpty);
    });

    testWithoutContext('skipped if nothing to migrate', () {
      const String flutterWindowContents = 'Nothing to migrate';

      flutterWindowFile.writeAsStringSync(flutterWindowContents);

      final DateTime updatedAt = flutterWindowFile.lastModifiedSync();
      final ShowWindowMigration migration = ShowWindowMigration(
        mockProject,
        testLogger,
      );
      migration.migrate();

      expect(flutterWindowFile.lastModifiedSync(), updatedAt);
      expect(flutterWindowFile.readAsStringSync(), flutterWindowContents);

      expect(testLogger.statusText, isEmpty);
    });

    testWithoutContext('skipped if already migrated', () {
      const String flutterWindowContents =
        '  flutter_controller_->engine()->SetNextFrameCallback([&]() {\n'
        '    this->Show();\n'
        '  });\n'
        '\n'
        '  // Flutter can complete the first frame before the "show window" callback is\n'
        '  // registered. The following call ensures a frame is pending to ensure the\n'
        "  // window is shown. It is a no-op if the first frame hasn't completed yet.\n"
        '  flutter_controller_->ForceRedraw();\n'
        '\n'
        '  return true;\n';

      flutterWindowFile.writeAsStringSync(flutterWindowContents);

      final DateTime updatedAt = flutterWindowFile.lastModifiedSync();
      final ShowWindowMigration migration = ShowWindowMigration(
        mockProject,
        testLogger,
      );
      migration.migrate();

      expect(flutterWindowFile.lastModifiedSync(), updatedAt);
      expect(flutterWindowFile.readAsStringSync(), flutterWindowContents);

      expect(testLogger.statusText, isEmpty);
    });

    testWithoutContext('skipped if already migrated (CRLF)', () {
      const String flutterWindowContents =
        '  flutter_controller_->engine()->SetNextFrameCallback([&]() {\r\n'
        '    this->Show();\r\n'
        '  });\r\n'
        '\r\n'
        '  // Flutter can complete the first frame before the "show window" callback is\r\n'
        '  // registered. The following call ensures a frame is pending to ensure the\r\n'
        "  // window is shown. It is a no-op if the first frame hasn't completed yet.\r\n"
        '  flutter_controller_->ForceRedraw();\r\n'
        '\r\n'
        '  return true;\r\n';

      flutterWindowFile.writeAsStringSync(flutterWindowContents);

      final DateTime updatedAt = flutterWindowFile.lastModifiedSync();
      final ShowWindowMigration migration = ShowWindowMigration(
        mockProject,
        testLogger,
      );
      migration.migrate();

      expect(flutterWindowFile.lastModifiedSync(), updatedAt);
      expect(flutterWindowFile.readAsStringSync(), flutterWindowContents);

      expect(testLogger.statusText, isEmpty);
    });

    testWithoutContext('migrates project to ensure window is shown', () {
      flutterWindowFile.writeAsStringSync(
        '  flutter_controller_->engine()->SetNextFrameCallback([&]() {\n'
        '    this->Show();\n'
        '  });\n'
        '\n'
        '  return true;\n'
      );

      final ShowWindowMigration migration = ShowWindowMigration(
        mockProject,
        testLogger,
      );
      migration.migrate();

      expect(flutterWindowFile.readAsStringSync(),
        '  flutter_controller_->engine()->SetNextFrameCallback([&]() {\n'
        '    this->Show();\n'
        '  });\n'
        '\n'
        '  // Flutter can complete the first frame before the "show window" callback is\n'
        '  // registered. The following call ensures a frame is pending to ensure the\n'
        "  // window is shown. It is a no-op if the first frame hasn't completed yet.\n"
        '  flutter_controller_->ForceRedraw();\n'
        '\n'
        '  return true;\n'
      );

      expect(testLogger.statusText, contains('windows/runner/flutter_window.cpp does not ensure the show window callback is called, updating.'));
    });

    testWithoutContext('migrates project to ensure window is shown (CRLF)', () {
      flutterWindowFile.writeAsStringSync(
        '  flutter_controller_->engine()->SetNextFrameCallback([&]() {\r\n'
        '    this->Show();\r\n'
        '  });\r\n'
        '\r\n'
        '  return true;\r\n'
      );

      final ShowWindowMigration migration = ShowWindowMigration(
        mockProject,
        testLogger,
      );
      migration.migrate();

      expect(flutterWindowFile.readAsStringSync(),
        '  flutter_controller_->engine()->SetNextFrameCallback([&]() {\r\n'
        '    this->Show();\r\n'
        '  });\r\n'
        '\r\n'
        '  // Flutter can complete the first frame before the "show window" callback is\r\n'
        '  // registered. The following call ensures a frame is pending to ensure the\r\n'
        "  // window is shown. It is a no-op if the first frame hasn't completed yet.\r\n"
        '  flutter_controller_->ForceRedraw();\r\n'
        '\r\n'
        '  return true;\r\n'
      );

      expect(testLogger.statusText, contains('windows/runner/flutter_window.cpp does not ensure the show window callback is called, updating.'));
    });
  });
}

class FakeWindowsProject extends Fake implements WindowsProject {
  FakeWindowsProject(this.runnerFlutterWindowFile);

  @override
  final File runnerFlutterWindowFile;
}
