// 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.

/// A fork of `package:flutter_goldens/flutter_goldens.dart` without the
/// dependency on `package:flutter_test` or `package:flutter`; this allows
/// the library to be used in a standalone Dart VM context.
library;

import 'dart:async';
import 'dart:io' as io;
import 'dart:typed_data';

import 'package:file/local.dart';
import 'package:flutter_goldens/skia_client.dart';
import 'package:meta/meta.dart';
import 'package:path/path.dart' as path;
import 'package:platform/platform.dart';
import 'package:process/process.dart';
import 'package:test_api/test_api.dart';

import 'native_driver.dart';

const LocalFileSystem _localFs = LocalFileSystem();
const String _kGoldctlKey = 'GOLDCTL';
const String _kGoldctlPresubmitKey = 'GOLD_TRYJOB';

/// Configures [goldenFileComparator] to use Skia Gold (i.e. on CI).
///
/// Requires that the `GOLDCTL` environment variable is set.
///
/// If the `GOLD_TRYJOB` environment variable is set, the test will be run in
/// presubmit mode; that is, the test will not fail if the comparison fails.
/// See <https://github.com/flutter/flutter/blob/main/docs/contributing/testing/Writing-a-golden-file-test-for-package-flutter.md>
/// for more information.
///
/// May optionally provide a [namePrefix] to be used when uploading images.
Future<void> enableSkiaGoldComparator({String? namePrefix}) async {
  assert(
    goldenFileComparator is NaiveLocalFileComparator,
    'The flutter_goldens_fork library should be used from a *_test.dart file '
    'where the "goldenFileComparator" has not yet been set. This is to ensure '
    'that the correct comparator is used for the current test environment.',
  );
  if (!io.Platform.environment.containsKey(_kGoldctlKey)) {
    throw StateError(
      'Environment variable $_kGoldctlKey is not set. '
      'Set it to use Skia Gold.',
    );
  }
  if (namePrefix != null) {
    assert(
      !namePrefix.endsWith('.'),
      'The namePrefix automatically has a suffix of ".", so remove the last character from "$namePrefix".',
    );
  }
  final io.Directory tmpDir = io.Directory.systemTemp.createTempSync('android_driver_test');
  final bool isPresubmit = io.Platform.environment.containsKey(_kGoldctlPresubmitKey);
  io.stderr.writeln(
    '=== Using Skia Gold ===\n'
    'Environment variable $_kGoldctlKey is set, using Skia Gold: \n'
    '  - tmpDir:      ${tmpDir.path}\n'
    '  - namePrefix:  $namePrefix\n'
    '  - isPresubmit: $isPresubmit\n',
  );
  final SkiaGoldClient skiaGoldClient = SkiaGoldClient(
    _localFs.directory(tmpDir.path),
    fs: _localFs,
    process: const LocalProcessManager(),
    platform: const LocalPlatform(),
    httpClient: io.HttpClient(),
    log: io.stderr.writeln,
  );
  await enableSkiaGoldComparatorForTesting(
    skiaGoldClient,
    namePrefix: namePrefix,
    presubmit: isPresubmit,
  );
}

/// Configures [goldenFileComparator] to use Skia Gold (for unit testing).
@visibleForTesting
Future<void> enableSkiaGoldComparatorForTesting(
  SkiaGoldClient skiaGoldClient, {
  required bool presubmit,
  String? namePrefix,
}) async {
  await skiaGoldClient.auth();
  goldenFileComparator = _SkiaGoldComparator(
    skiaGoldClient,
    namePrefix: namePrefix,
    isPresubmit: presubmit,
  );
}

final class _SkiaGoldComparator extends GoldenFileComparator {
  _SkiaGoldComparator(this.skiaClient, {required this.isPresubmit, this.namePrefix, Uri? baseDir})
    : baseDir = baseDir ?? Uri.parse(path.dirname(io.Platform.script.path));

  final Uri baseDir;
  final SkiaGoldClient skiaClient;
  final String? namePrefix;
  final bool isPresubmit;

  @override
  Future<bool> compare(Uint8List imageBytes, Uri golden) async {
    if (isPresubmit) {
      await skiaClient.tryjobInit();
    } else {
      await skiaClient.imgtestInit();
    }

    golden = _addPrefix(golden);
    final io.File goldenFile = await update(golden, imageBytes);
    if (isPresubmit) {
      final String? result = await skiaClient.tryjobAdd(
        golden.path,
        _localFs.file(goldenFile.path),
      );
      if (result != null) {
        io.stderr.writeln('Skia Gold detected an error when comparing "$golden":\n\n$result');
        io.stderr.writeln('Still succeeding, will be triaged in Flutter Gold');
      } else {
        io.stderr.writeln('Skia Gold comparison succeeded comparing "$golden".');
      }
      return true;
    }

    try {
      return await skiaClient.imgtestAdd(golden.path, _localFs.file(goldenFile.path));
    } on SkiaException catch (e) {
      // Convert SkiaException -> TestFailure so that this class implements the
      // contract of GoldenFileComparator, and matchesGoldenFile() converts the
      // TestFailure into a standard reported test error (with a better stack
      // trace, for example).
      //
      // https://github.com/flutter/flutter/issues/162621
      throw TestFailure('$e');
    }
  }

  @override
  Future<io.File> update(Uri golden, Uint8List imageBytes) async {
    io.stderr.writeln('Updating golden file: $golden (${imageBytes.length} bytes)...');
    final io.File goldenFile = _getGoldenFile(golden);
    await goldenFile.parent.create(recursive: true);
    await goldenFile.writeAsBytes(imageBytes, flush: true);
    return goldenFile;
  }

  io.File _getGoldenFile(Uri uri) {
    return io.File.fromUri(baseDir.resolveUri(uri));
  }

  Uri _addPrefix(Uri golden) {
    assert(
      golden.toString().split('.').last == 'png',
      'Golden files in the Flutter framework must end with the file extension '
      '.png.',
    );
    return Uri.parse(<String>[if (namePrefix != null) namePrefix!, golden.toString()].join('.'));
  }
}
