// Copyright (c) 2023, 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.

import 'dart:developer';

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

import 'common/service_test_common.dart';
import 'common/test_helper.dart';

// AUTOGENERATED START
//
// Update these constants by running:
//
// dart pkg/vm_service/test/update_line_numbers.dart pkg/vm_service/test/get_source_report_test.dart
//
const LINE_A = 38;
const LINE_B = 61;
const OFFSET_START = 0872;
const OFFSET_COMPARE = 0943;
const OFFSET_PRINT_NEGATIVE = 0984;
const OFFSET_PRINT_POSITIVE = 1050;
const OFFSET_DEBUGGER = 1101;
const OFFSET_END = 1146;
// AUTOGENERATED END

int globalVar = 100;

class MyClass {
  /* OFFSET_START */ static void myFunction(int value) {
    if (value /* OFFSET_COMPARE */ < 0) {
      /* OFFSET_PRINT_NEGATIVE */ print('negative');
    } else {
      /* OFFSET_PRINT_POSITIVE */ print('positive');
    }
    /* OFFSET_DEBUGGER */ debugger(); // LINE_A
    /* OFFSET_END */
  }

  static void otherFunction(int value) {
    if (value < 0) {
      print('otherFunction <');
    } else {
      print('otherFunction >=');
    }
  }
}

void testFunction() {
  MyClass.otherFunction(-100);
  MyClass.myFunction(10000);
}

class MyConstClass {
  const MyConstClass();
  static const MyConstClass instance = MyConstClass();

  void foo() {
    debugger(); // LINE_B
  }
}

void testFunction2() {
  MyConstClass.instance.foo();
}

bool allRangesCompiled(SourceReport coverage) {
  for (final range in coverage.ranges!) {
    if (!range.compiled!) {
      return false;
    }
  }
  return true;
}

final tests = <IsolateTest>[
  hasStoppedAtBreakpoint,
  stoppedAtLine(LINE_A),
  (VmService service, IsolateRef isolateRef) async {
    final isolateId = isolateRef.id!;
    final stack = await service.getStack(isolateId);
    final func = stack.frames!.first.function!;
    final scriptId = func.location!.script!.id!;

    final expectedRange = SourceReportRange(
      scriptIndex: 0,
      startPos: OFFSET_START,
      endPos: OFFSET_END,
      compiled: true,
      coverage: SourceReportCoverage(
        hits: const [
          OFFSET_START,
          OFFSET_COMPARE,
          OFFSET_PRINT_POSITIVE,
          OFFSET_DEBUGGER,
        ],
        misses: [OFFSET_PRINT_NEGATIVE],
      ),
    );

    // Full script
    var coverage = await service.getSourceReport(
      isolateId,
      [SourceReportKind.kCoverage],
      scriptId: scriptId,
    );

    var ranges = coverage.ranges!;
    expect(ranges.length, greaterThanOrEqualTo(10));
    // TODO(bkonyi): implement operator== properly.
    expect(ranges[0].toJson(), expectedRange.toJson());

    var scripts = coverage.scripts!;
    expect(coverage.scripts!.length, 1);
    expect(scripts[0].uri!, endsWith('get_source_report_test.dart'));
    expect(allRangesCompiled(coverage), false);

    // Force compilation.
    coverage = await service.getSourceReport(
      isolateId,
      [SourceReportKind.kCoverage],
      scriptId: scriptId,
      forceCompile: true,
    );
    ranges = coverage.ranges!;
    expect(ranges.length, greaterThanOrEqualTo(10));
    expect(allRangesCompiled(coverage), isTrue);

    // One function
    coverage = await service.getSourceReport(
      isolateId,
      [SourceReportKind.kCoverage],
      scriptId: scriptId,
      tokenPos: func.location!.tokenPos!,
      endTokenPos: func.location!.endTokenPos!,
    );
    ranges = coverage.ranges!;
    scripts = coverage.scripts!;
    expect(ranges.length, 1);
    // TODO(bkonyi): implement operator== properly.
    expect(ranges[0].toJson(), expectedRange.toJson());
    expect(scripts.length, 1);
    expect(scripts[0].uri!, endsWith('get_source_report_test.dart'));

    // Full isolate
    coverage = await service.getSourceReport(
      isolateId,
      [SourceReportKind.kCoverage],
    );
    ranges = coverage.ranges!;
    scripts = coverage.scripts!;
    expect(ranges.length, greaterThan(1));
    expect(scripts.length, greaterThan(1));

    // Full isolate
    coverage = await service.getSourceReport(
      isolateId,
      [SourceReportKind.kCoverage],
      forceCompile: true,
    );
    ranges = coverage.ranges!;
    scripts = coverage.scripts!;
    expect(ranges.length, greaterThan(1));
    expect(scripts.length, greaterThan(1));

    // Multiple reports (make sure enum list parameter parsing works).
    coverage = await service.getSourceReport(
      isolateId,
      [
        SourceReportKind.kCoverage,
        SourceReportKind.kPossibleBreakpoints,
        '_CallSites',
      ],
      scriptId: scriptId,
      tokenPos: func.location!.tokenPos!,
      endTokenPos: func.location!.endTokenPos!,
    );
    ranges = coverage.ranges!;
    expect(ranges.length, 1);
    final range = ranges[0];
    expect(coverage.json!['ranges'][0].containsKey('callSites'), true);
    expect(range.coverage, isNotNull);
    expect(range.possibleBreakpoints, isNotNull);

    // missing scriptId with tokenPos.
    bool caughtException = false;
    try {
      await service.getSourceReport(
        isolateId,
        [SourceReportKind.kCoverage],
        tokenPos: func.location!.tokenPos!,
      );
      fail('Unreachable');
    } on RPCError catch (e) {
      caughtException = true;
      expect(e.code, RPCErrorKind.kInvalidParams.code);
      expect(
          e.details,
          "getSourceReport: the 'tokenPos' parameter requires the "
          "'scriptId' parameter");
    }
    expect(caughtException, true);

    // missing scriptId with endTokenPos.
    caughtException = false;
    try {
      await service.getSourceReport(
        isolateId,
        [SourceReportKind.kCoverage],
        endTokenPos: func.location!.endTokenPos!,
      );
      fail('Unreachable');
    } on RPCError catch (e) {
      caughtException = true;
      expect(e.code, RPCErrorKind.kInvalidParams.code);
      expect(
          e.details,
          "getSourceReport: the 'endTokenPos' parameter requires the "
          "'scriptId' parameter");
    }
    expect(caughtException, true);
  },
];

void main([args = const <String>[]]) => runIsolateTests(
      args,
      tests,
      'get_source_report_test.dart',
      testeeConcurrent: testFunction,
    );
