// 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:convert';

import 'package:clock/clock.dart';
import 'package:file/file.dart';
import 'package:file/memory.dart';
import 'package:test/test.dart';

import 'package:unified_analytics/src/constants.dart';
import 'package:unified_analytics/src/enums.dart';
import 'package:unified_analytics/src/survey_handler.dart';
import 'package:unified_analytics/src/utils.dart';
import 'package:unified_analytics/unified_analytics.dart';

void main() {
  final testEvent = Event.hotReloadTime(timeMs: 10);

  group('Unit testing function sampleRate:', () {
    // Set a string that can be used in place of a survey's unique ID
    final iterations = 1000;
    final uuid = Uuid(123);
    final uniqueSurveyId = uuid.generateV4();

    // Set how much the actual sampled rate can be (allowing 5% of variability)
    final marginOfError = 0.05;

    test('Unit testing the sampleRate method', () {
      // These strings had a predetermined output from the utility function
      final string1 = 'string1';
      final string2 = 'string2';
      expect(sampleRate(string1, string2), 0.40);
    });

    test('Simulating with various sample rates', () {
      final sampleRateToTestList = [
        0.10,
        0.25,
        0.50,
        0.75,
        0.80,
        0.95,
      ];
      for (final sampleRateToTest in sampleRateToTestList) {
        var count = 0;
        for (var i = 0; i < iterations; i++) {
          // Regenerate the client id to simulate a unique user
          final generatedClientId = uuid.generateV4();
          if (sampleRate(uniqueSurveyId, generatedClientId) <=
              sampleRateToTest) {
            count += 1;
          }
        }

        final actualSampledRate = count / iterations;
        final actualMarginOfError =
            (sampleRateToTest - actualSampledRate).abs();

        expect(actualMarginOfError < marginOfError, true,
            reason: 'Failed on sample rate = $sampleRateToTest with'
                ' actual rate $actualMarginOfError '
                'and a margin of error = $marginOfError');
      }
    });
  });

  group('Unit testing function checkSurveyDate:', () {
    final date = DateTime(2023, 5, 1);
    // Two surveys created, one that is within the survey date
    // range, and one that is not
    final validSurvey = Survey(
      uniqueId: 'uniqueId',
      startDate: DateTime(2023, 1, 1),
      endDate: DateTime(2023, 12, 31),
      description: 'description',
      snoozeForMinutes: 10,
      samplingRate: 1.0,
      excludeDashToolList: [],
      conditionList: <Condition>[],
      buttonList: [],
    );
    final invalidSurvey = Survey(
      uniqueId: 'uniqueId',
      startDate: DateTime(2022, 1, 1),
      endDate: DateTime(2022, 12, 31),
      description: 'description',
      snoozeForMinutes: 10,
      samplingRate: 1.0,
      excludeDashToolList: [],
      conditionList: <Condition>[],
      buttonList: [],
    );

    test('expired survey', () {
      final clock = Clock.fixed(date);
      withClock(clock, () {
        expect(SurveyHandler.checkSurveyDate(invalidSurvey), false);
      });
    });

    test('valid survey', () {
      final clock = Clock.fixed(date);
      withClock(clock, () {
        expect(SurveyHandler.checkSurveyDate(validSurvey), true);
      });
    });
  });

  group('Unit testing function parseSurveysFromJson', () {
    final validContents = '''
[
  {
    "uniqueId": "xxxxx",
    "startDate": "2023-06-01T09:00:00-07:00",
    "endDate": "2023-06-30T09:00:00-07:00",
    "description": "xxxxxxx",
    "snoozeForMinutes": "10",
    "samplingRate": "1.0",
    "excludeDashTools": [],
    "conditions": [
      {
        "field": "logFileStats.recordCount",
        "operator": ">=",
        "value": 1000
      },
      {
        "field": "logFileStats.toolCount.flutter-tool",
        "operator": "<",
        "value": 3
      }
    ],
    "buttons": [
        {
            "buttonText": "Take Survey",
            "action": "accept",
            "url": "https://google.qualtrics.com/jfe/form/SV_5gsB2EuG324y2",
            "promptRemainsVisible": false
        }
    ]
  }
]
''';

    // The value for the condition is not a valid integer
    final invalidConditionValueContents = '''
[
  {
    "uniqueId": "xxxxx",
    "startDate": "2023-06-01T09:00:00-07:00",
    "endDate": "2023-06-30T09:00:00-07:00",
    "description": "xxxxxxx",
    "snoozeForMinutes": "10",
    "samplingRate": "1.0",
    "excludeDashTools": [],
    "conditions": [
      {
        "field": "logFileStats.recordCount",
        "operator": ">=",
        "value": "1000xxxx"
      }
    ],
    "buttons": [
        {
            "buttonText": "Take Survey",
            "action": "accept",
            "url": "https://google.qualtrics.com/jfe/form/SV_5gsB2EuG324y2",
            "promptRemainsVisible": false
        }
    ]
  }
]
''';

    // Using a dash tool in the excludeDashTools array that is not a valid
    // DashTool label
    final invalidDashToolContents = '''
[
  {
    "uniqueId": "xxxxx",
    "startDate": "2023-06-01T09:00:00-07:00",
    "endDate": "2023-06-30T09:00:00-07:00",
    "description": "xxxxxxx",
    "snoozeForMinutes": "10",
    "samplingRate": "1.0",
    "excludeDashTools": [
      "not-a-valid-dash-tool"
    ],
    "conditions": [
      {
        "field": "logFileStats.recordCount",
        "operator": ">=",
        "value": 1000
      },
      {
        "field": "logFileStats.toolCount.flutter-tool",
        "operator": "<",
        "value": 3
      }
    ],
    "buttons": [
        {
            "buttonText": "Take Survey",
            "action": "accept",
            "url": "https://google.qualtrics.com/jfe/form/SV_5gsB2EuG324y2",
            "promptRemainsVisible": false
        }
    ]
  }
]
''';

    test('valid json', () {
      withClock(Clock.fixed(DateTime(2023, 6, 15)), () {
        final parsedSurveys = SurveyHandler.parseSurveysFromJson(
            jsonDecode(validContents) as List);

        expect(parsedSurveys.length, 1);
        expect(parsedSurveys.first.conditionList.length, 2);

        final firstCondition = parsedSurveys.first.conditionList.first;
        final secondCondition = parsedSurveys.first.conditionList[1];

        expect(firstCondition.field, 'logFileStats.recordCount');
        expect(firstCondition.operatorString, '>=');
        expect(firstCondition.value, 1000);

        expect(secondCondition.field, 'logFileStats.toolCount.flutter-tool');
        expect(secondCondition.operatorString, '<');
        expect(secondCondition.value, 3);

        expect(parsedSurveys.first.buttonList.length, 1);
        expect(
            parsedSurveys.first.buttonList.first.promptRemainsVisible, false);
      });
    });

    test('invalid condition json', () {
      withClock(Clock.fixed(DateTime(2023, 6, 15)), () {
        final parsedSurveys = SurveyHandler.parseSurveysFromJson(
            jsonDecode(invalidConditionValueContents) as List);

        expect(parsedSurveys.length, 0,
            reason: 'The condition value is not a '
                'proper integer so it should error returning no surveys');
      });
    });

    test('invalid dash tool json', () {
      withClock(Clock.fixed(DateTime(2023, 6, 15)), () {
        final parsedSurveys = SurveyHandler.parseSurveysFromJson(
            jsonDecode(invalidDashToolContents) as List);

        expect(parsedSurveys.length, 0,
            reason: 'The dash tool in the exclude array is not valid '
                'so it should error returning no surveys');
      });
    });
  });

  group('Testing with FakeSurveyHandler', () {
    late Analytics analytics;
    late Directory homeDirectory;
    late MemoryFileSystem fs;
    late File clientIdFile;
    late File dismissedSurveyFile;

    setUp(() {
      fs = MemoryFileSystem.test(style: FileSystemStyle.posix);
      homeDirectory = fs.directory('home');

      // Write the client ID file out so that we don't get
      // a randomly assigned id for this test generated within
      // the analytics constructor
      clientIdFile = homeDirectory
          .childDirectory(kDartToolDirectoryName)
          .childFile(kClientIdFileName);
      clientIdFile.createSync(recursive: true);
      clientIdFile.writeAsStringSync('string1');

      // Assign the json file that will hold the persisted surveys
      dismissedSurveyFile = homeDirectory
          .childDirectory(kDartToolDirectoryName)
          .childFile(kDismissedSurveyFileName);

      // Setup two tools to be onboarded with this package so
      // that we can simulate two different tools interacting with
      // surveys
      //
      // This is especially useful when testing the "excludeDashTools" array
      // to prevent certain tools from getting a survey from this package
      final initialAnalyticsFlutter = Analytics.fake(
        tool: DashTool.flutterTool,
        homeDirectory: homeDirectory,
        dartVersion: 'dartVersion',
        fs: fs,
        platform: DevicePlatform.macos,
      );
      final initialAnalyticsDart = Analytics.fake(
        tool: DashTool.dartTool,
        homeDirectory: homeDirectory,
        dartVersion: 'dartVersion',
        fs: fs,
        platform: DevicePlatform.macos,
      );
      initialAnalyticsFlutter.clientShowedMessage();
      initialAnalyticsDart.clientShowedMessage();
    });

    test('returns valid survey', () async {
      await withClock(Clock.fixed(DateTime(2023, 3, 3)), () async {
        analytics = Analytics.fake(
          tool: DashTool.flutterTool,
          homeDirectory: homeDirectory,
          dartVersion: 'dartVersion',
          fs: fs,
          platform: DevicePlatform.macos,
          surveyHandler: FakeSurveyHandler.fromList(
            dismissedSurveyFile: dismissedSurveyFile,
            initializedSurveys: <Survey>[
              Survey(
                uniqueId: 'uniqueId',
                startDate: DateTime(2023, 1, 1),
                endDate: DateTime(2023, 12, 31),
                description: 'description',
                snoozeForMinutes: 10,
                samplingRate: 1.0,
                excludeDashToolList: [],
                conditionList: <Condition>[
                  Condition('logFileStats.recordCount', '>=', 50),
                  Condition('logFileStats.toolCount.flutter-tool', '>', 0),
                ],
                buttonList: [
                  SurveyButton(
                    buttonText: 'buttonText',
                    action: 'accept',
                    url: 'http://example.com',
                    promptRemainsVisible: false,
                  ),
                ],
              ),
            ],
          ),
        );

        // Simulate 60 events to send so that the first condition is satisified
        for (var i = 0; i < 60; i++) {
          analytics.send(testEvent);
        }

        final fetchedSurveys = await analytics.fetchAvailableSurveys();

        expect(fetchedSurveys.length, 1);

        final survey = fetchedSurveys.first;
        expect(survey.conditionList.length, 2);
        expect(survey.buttonList.length, 1);
      });
    });

    test('does not return expired survey', () async {
      await withClock(Clock.fixed(DateTime(2023, 3, 3)), () async {
        analytics = Analytics.fake(
          tool: DashTool.flutterTool,
          homeDirectory: homeDirectory,
          dartVersion: 'dartVersion',
          fs: fs,
          platform: DevicePlatform.macos,
          surveyHandler: FakeSurveyHandler.fromList(
            dismissedSurveyFile: dismissedSurveyFile,
            initializedSurveys: <Survey>[
              Survey(
                uniqueId: 'uniqueId',
                startDate: DateTime(2022, 1, 1),
                endDate: DateTime(2022, 12, 31),
                description: 'description',
                snoozeForMinutes: 10,
                samplingRate: 1.0,
                excludeDashToolList: [],
                conditionList: <Condition>[
                  Condition('logFileStats.recordCount', '>=', 50),
                  Condition('logFileStats.toolCount.flutter-tool', '>', 0),
                ],
                buttonList: [],
              ),
            ],
          ),
        );

        // Simulate 60 events to send so that the first condition is satisified
        for (var i = 0; i < 60; i++) {
          analytics.send(testEvent);
        }

        final fetchedSurveys = await analytics.fetchAvailableSurveys();

        expect(fetchedSurveys.length, 0);
      });
    });

    test('does not return survey if opted out of telemetry', () async {
      await withClock(Clock.fixed(DateTime(2023, 3, 3)), () async {
        analytics = Analytics.fake(
          tool: DashTool.flutterTool,
          homeDirectory: homeDirectory,
          dartVersion: 'dartVersion',
          fs: fs,
          platform: DevicePlatform.macos,
          surveyHandler: FakeSurveyHandler.fromList(
            dismissedSurveyFile: dismissedSurveyFile,
            initializedSurveys: <Survey>[
              Survey(
                uniqueId: 'uniqueId',
                startDate: DateTime(2023, 1, 1),
                endDate: DateTime(2023, 12, 31),
                description: 'description',
                snoozeForMinutes: 10,
                samplingRate: 1.0,
                excludeDashToolList: [],
                conditionList: <Condition>[
                  Condition('logFileStats.recordCount', '>=', 50),
                  Condition('logFileStats.toolCount.flutter-tool', '>', 0),
                ],
                buttonList: [],
              ),
            ],
          ),
        );

        // Simulate 60 events to send so that the first condition is satisified
        for (var i = 0; i < 60; i++) {
          analytics.send(testEvent);
        }

        await analytics.setTelemetry(false);
        expect(analytics.okToSend, false);

        final fetchedSurveys = await analytics.fetchAvailableSurveys();

        expect(fetchedSurveys.length, 0);
      });
    });

    test('returns valid survey from json', () async {
      await withClock(Clock.fixed(DateTime(2023, 3, 3)), () async {
        analytics = Analytics.fake(
          tool: DashTool.flutterTool,
          homeDirectory: homeDirectory,
          dartVersion: 'dartVersion',
          fs: fs,
          platform: DevicePlatform.macos,
          surveyHandler: FakeSurveyHandler.fromString(
              dismissedSurveyFile: dismissedSurveyFile, content: '''
[
    {
        "uniqueId": "uniqueId123",
        "startDate": "2023-01-01T09:00:00-07:00",
        "endDate": "2023-12-31T09:00:00-07:00",
        "description": "description123",
        "snoozeForMinutes": "10",
        "samplingRate": "1.0",
        "excludeDashTools": [],
        "conditions": [
            {
                "field": "logFileStats.recordCount",
                "operator": ">=",
                "value": 50
            }
        ],
        "buttons": [
            {
                "buttonText": "Take Survey",
                "action": "accept",
                "url": "https://google.qualtrics.com/jfe/form/SV_5gsB2EuG324y2",
                "promptRemainsVisible": false
            },
            {
                "buttonText": "Dismiss",
                "action": "dismiss",
                "url": null,
                "promptRemainsVisible": false
            },
            {
                "buttonText": "More Info",
                "action": "snooze",
                "url": "https://docs.flutter.dev/reference/crash-reporting",
                "promptRemainsVisible": true
            }
        ]
    }
]
'''),
        );

        // Simulate 60 events to send so that the first condition is satisified
        for (var i = 0; i < 60; i++) {
          analytics.send(testEvent);
        }

        final fetchedSurveys = await analytics.fetchAvailableSurveys();

        expect(fetchedSurveys.length, 1);

        final survey = fetchedSurveys.first;
        expect(survey.uniqueId, 'uniqueId123');
        expect(survey.startDate.year, 2023);
        expect(survey.startDate.month, 1);
        expect(survey.startDate.day, 1);
        expect(survey.endDate.year, 2023);
        expect(survey.endDate.month, 12);
        expect(survey.endDate.day, 31);
        expect(survey.description, 'description123');
        expect(survey.snoozeForMinutes, 10);
        expect(survey.samplingRate, 1.0);
        expect(survey.conditionList.length, 1);

        final condition = survey.conditionList.first;
        expect(condition.field, 'logFileStats.recordCount');
        expect(condition.operatorString, '>=');
        expect(condition.value, 50);

        final buttonList = survey.buttonList;
        expect(buttonList.length, 3);
        expect(buttonList.first.buttonText, 'Take Survey');
        expect(buttonList.first.action, 'accept');
        expect(buttonList.first.url,
            'https://google.qualtrics.com/jfe/form/SV_5gsB2EuG324y2');
        expect(buttonList.first.promptRemainsVisible, false);

        expect(buttonList.elementAt(1).buttonText, 'Dismiss');
        expect(buttonList.elementAt(1).action, 'dismiss');
        expect(buttonList.elementAt(1).url, isNull);
        expect(buttonList.elementAt(1).promptRemainsVisible, false);

        expect(buttonList.last.buttonText, 'More Info');
        expect(buttonList.last.action, 'snooze');
        expect(buttonList.last.url,
            'https://docs.flutter.dev/reference/crash-reporting');
        expect(buttonList.last.promptRemainsVisible, true);
      });
    });

    test('no survey returned from malformed json', () async {
      // The date is not valid for the start date
      await withClock(Clock.fixed(DateTime(2023, 3, 3)), () async {
        analytics = Analytics.fake(
          tool: DashTool.flutterTool,
          homeDirectory: homeDirectory,
          dartVersion: 'dartVersion',
          fs: fs,
          platform: DevicePlatform.macos,
          surveyHandler: FakeSurveyHandler.fromString(
              dismissedSurveyFile: dismissedSurveyFile, content: '''
[
    {
        "uniqueId": "uniqueId123",
        "startDate": "NOT A REAL DATE",
        "endDate": "2023-07-30T09:00:00-07:00",
        "description": "Help improve Flutter's release builds with this 3-question survey!",
        "snoozeForMinutes": "7200",
        "samplingRate": "0.1",
        "excludeDashTools": [],
        "conditions": [
            {
                "field": "logFileStats.recordCount",
                "operator": ">=",
                "value": 50
            }
        ],
        "buttons": [
            {
                "buttonText": "Take Survey",
                "action": "accept",
                "url": "https://google.qualtrics.com/jfe/form/SV_5gsB2EuG5Et5Yy2",
                "promptRemainsVisible": false
            },
            {
                "buttonText": "Dismiss",
                "action": "dismiss",
                "url": null,
                "promptRemainsVisible": false
            },
            {
                "buttonText": "More Info",
                "action": "snooze",
                "url": "https://docs.flutter.dev/reference/crash-reporting",
                "promptRemainsVisible": false
            }
        ]
    }
]
'''),
        );

        // Simulate 60 events to send so that the first condition is satisified
        for (var i = 0; i < 60; i++) {
          analytics.send(testEvent);
        }

        final fetchedSurveys = await analytics.fetchAvailableSurveys();

        expect(fetchedSurveys.length, 0);
      });
    });

    test('returns two valid survey from json', () async {
      await withClock(Clock.fixed(DateTime(2023, 3, 3)), () async {
        analytics = Analytics.fake(
          tool: DashTool.flutterTool,
          homeDirectory: homeDirectory,
          dartVersion: 'dartVersion',
          fs: fs,
          platform: DevicePlatform.macos,
          surveyHandler: FakeSurveyHandler.fromString(
              dismissedSurveyFile: dismissedSurveyFile, content: '''
[
    {
        "uniqueId": "12345",
        "startDate": "2023-01-01T09:00:00-07:00",
        "endDate": "2023-12-31T09:00:00-07:00",
        "description": "xxxxxxx",
        "snoozeForMinutes": "10",
        "samplingRate": "1.0",
        "excludeDashTools": [],
        "conditions": [
            {
                "field": "logFileStats.recordCount",
                "operator": ">=",
                "value": 50
            }
        ], 
        "buttons": []
    },
    {
        "uniqueId": "67890",
        "startDate": "2023-01-01T09:00:00-07:00",
        "endDate": "2023-12-31T09:00:00-07:00",
        "description": "xxxxxxx",
        "snoozeForMinutes": "10",
        "samplingRate": "1.0",
        "excludeDashTools": [],
        "conditions": [
            {
                "field": "logFileStats.recordCount",
                "operator": ">=",
                "value": 50
            }
        ],
        "buttons": [
            {
                "buttonText": "More Info",
                "action": "snooze",
                "url": "https://docs.flutter.dev/reference/crash-reporting",
                "promptRemainsVisible": true
            }
        ]
    }
]
'''),
        );

        // Simulate 60 events to send so that the first condition is satisified
        for (var i = 0; i < 60; i++) {
          analytics.send(testEvent);
        }

        final fetchedSurveys = await analytics.fetchAvailableSurveys();

        expect(fetchedSurveys.length, 2);

        final firstSurvey = fetchedSurveys.first;
        final secondSurvey = fetchedSurveys.last;

        expect(firstSurvey.uniqueId, '12345');
        expect(secondSurvey.uniqueId, '67890');

        final secondSurveyButtons = secondSurvey.buttonList;
        expect(secondSurveyButtons.length, 1);
        expect(secondSurveyButtons.first.buttonText, 'More Info');
        expect(secondSurveyButtons.first.action, 'snooze');
        expect(secondSurveyButtons.first.url,
            'https://docs.flutter.dev/reference/crash-reporting');
        expect(secondSurveyButtons.first.promptRemainsVisible, true);
      });
    });

    test('valid survey not returned if opted out', () async {
      await withClock(Clock.fixed(DateTime(2023, 3, 3)), () async {
        analytics = Analytics.fake(
          tool: DashTool.flutterTool,
          homeDirectory: homeDirectory,
          dartVersion: 'dartVersion',
          fs: fs,
          platform: DevicePlatform.macos,
          surveyHandler: FakeSurveyHandler.fromList(
            dismissedSurveyFile: dismissedSurveyFile,
            initializedSurveys: <Survey>[
              Survey(
                uniqueId: 'uniqueId',
                startDate: DateTime(2023, 1, 1),
                endDate: DateTime(2023, 12, 31),
                description: 'description',
                snoozeForMinutes: 10,
                samplingRate: 1.0,
                excludeDashToolList: [],
                conditionList: <Condition>[
                  Condition('logFileStats.recordCount', '>=', 50),
                  Condition('logFileStats.toolCount.flutter-tool', '>', 0),
                ],
                buttonList: [],
              ),
            ],
          ),
        );

        // Simulate 60 events to send so that the first condition is satisified
        for (var i = 0; i < 60; i++) {
          analytics.send(testEvent);
        }

        // Setting to false will prevent anything from getting returned
        await analytics.setTelemetry(false);
        var fetchedSurveys = await analytics.fetchAvailableSurveys();
        expect(fetchedSurveys.length, 0);

        // Setting telemetry back to true should enable the surveys to get
        // returned again; we will also need to send the fake events again
        // because on opt out, the log file will get cleared and one of
        // the conditions for the fake survey loaded is that we need
        // at least 50 records for one of the conditions
        await analytics.setTelemetry(true);
        for (var i = 0; i < 60; i++) {
          analytics.send(testEvent);
        }
        fetchedSurveys = await analytics.fetchAvailableSurveys();
        expect(fetchedSurveys.length, 1);
      });
    });

    test('Sampling rate correctly returns a valid survey', () async {
      // This test will use a predefined client ID string of `string1`
      // which has been set in the setup along with a predefined
      // string for the survey ID of `string2` to get a sample rate value
      //
      // The combination of `string1` and `string2` will return 0.40
      // from the sampleRate utility function so we have set the threshold
      // to be 0.6 which should return surveys
      await withClock(Clock.fixed(DateTime(2023, 3, 3)), () async {
        final survey = Survey(
          uniqueId: 'string2',
          startDate: DateTime(2023, 1, 1),
          endDate: DateTime(2023, 12, 31),
          description: 'description',
          snoozeForMinutes: 10,
          samplingRate: 0.6,
          excludeDashToolList: [],
          conditionList: <Condition>[
            Condition('logFileStats.recordCount', '>=', 50),
            Condition('logFileStats.toolCount.flutter-tool', '>', 0),
          ],
          buttonList: [],
        );
        analytics = Analytics.fake(
          tool: DashTool.flutterTool,
          homeDirectory: homeDirectory,
          dartVersion: 'dartVersion',
          fs: fs,
          platform: DevicePlatform.macos,
          surveyHandler: FakeSurveyHandler.fromList(
            dismissedSurveyFile: dismissedSurveyFile,
            initializedSurveys: <Survey>[survey],
          ),
        );

        // Simulate 60 events to send so that the first condition is satisified
        for (var i = 0; i < 60; i++) {
          analytics.send(testEvent);
        }

        final fetchedSurveys = await analytics.fetchAvailableSurveys();

        expect(survey.samplingRate, 0.6);
        expect(fetchedSurveys.length, 1);
      });
    });

    test('Sampling rate filters out a survey', () async {
      // We will reduce the survey's sampling rate to be 0.3 which is
      // less than value returned from the predefined client ID and
      // survey sample
      await withClock(Clock.fixed(DateTime(2023, 3, 3)), () async {
        final survey = Survey(
          uniqueId: 'string2',
          startDate: DateTime(2023, 1, 1),
          endDate: DateTime(2023, 12, 31),
          description: 'description',
          snoozeForMinutes: 10,
          samplingRate: 0.15,
          excludeDashToolList: [],
          conditionList: <Condition>[
            Condition('logFileStats.recordCount', '>=', 50),
            Condition('logFileStats.toolCount.flutter-tool', '>', 0),
          ],
          buttonList: [],
        );
        analytics = Analytics.fake(
          tool: DashTool.flutterTool,
          homeDirectory: homeDirectory,
          dartVersion: 'dartVersion',
          fs: fs,
          platform: DevicePlatform.macos,
          surveyHandler: FakeSurveyHandler.fromList(
            dismissedSurveyFile: dismissedSurveyFile,
            initializedSurveys: <Survey>[survey],
          ),
        );

        // Simulate 60 events to send so that the first condition is satisified
        for (var i = 0; i < 60; i++) {
          analytics.send(testEvent);
        }

        final fetchedSurveys = await analytics.fetchAvailableSurveys();

        expect(survey.samplingRate, 0.15);
        expect(fetchedSurveys.length, 0);
      });
    });

    test('Snoozing survey is successful with snooze timeout from survey',
        () async {
      expect(dismissedSurveyFile.readAsStringSync(), '{}',
          reason: 'Should be an empty object');

      // Initialize the survey class that we will use for this test
      final minutesToSnooze = 30;
      final surveyToLoad = Survey(
        uniqueId: 'uniqueId',
        startDate: DateTime(2023, 1, 1),
        endDate: DateTime(2023, 12, 31),
        description: 'description',
        snoozeForMinutes:
            minutesToSnooze, // Initialized survey with `minutesToSnooze`
        samplingRate: 1.0,
        excludeDashToolList: [],
        conditionList: <Condition>[],
        buttonList: [],
      );

      await withClock(Clock.fixed(DateTime(2023, 3, 3, 12, 0)), () async {
        analytics = Analytics.fake(
          tool: DashTool.flutterTool,
          homeDirectory: homeDirectory,
          dartVersion: 'dartVersion',
          fs: fs,
          platform: DevicePlatform.macos,
          surveyHandler: FakeSurveyHandler.fromList(
            dismissedSurveyFile: dismissedSurveyFile,
            initializedSurveys: <Survey>[surveyToLoad],
          ),
        );

        final fetchedSurveys = await analytics.fetchAvailableSurveys();
        expect(fetchedSurveys.length, 1);

        final survey = fetchedSurveys.first;
        expect(survey.snoozeForMinutes, minutesToSnooze);

        // We will snooze the survey now and it should not show up
        // if we fetch surveys again before the minutes to snooze time
        // has finished
        analytics.surveyShown(survey);
      });

      // This analytics instance will be simulated to be shortly after the first
      // snooze, but before the snooze period has elapsed
      await withClock(Clock.fixed(DateTime(2023, 3, 3, 12, 15)), () async {
        analytics = Analytics.fake(
          tool: DashTool.flutterTool,
          homeDirectory: homeDirectory,
          dartVersion: 'dartVersion',
          fs: fs,
          platform: DevicePlatform.macos,
          surveyHandler: FakeSurveyHandler.fromList(
            dismissedSurveyFile: dismissedSurveyFile,
            initializedSurveys: <Survey>[surveyToLoad],
          ),
        );

        final fetchedSurveys = await analytics.fetchAvailableSurveys();
        expect(fetchedSurveys.length, 0,
            reason: 'The snooze period has not elapsed yet');
      });

      // This analytics instance will be simulated to be after the snooze period
      await withClock(Clock.fixed(DateTime(2023, 3, 3, 12, 35)), () async {
        analytics = Analytics.fake(
          tool: DashTool.flutterTool,
          homeDirectory: homeDirectory,
          dartVersion: 'dartVersion',
          fs: fs,
          platform: DevicePlatform.macos,
          surveyHandler: FakeSurveyHandler.fromList(
            dismissedSurveyFile: dismissedSurveyFile,
            initializedSurveys: <Survey>[surveyToLoad],
          ),
        );

        final fetchedSurveys = await analytics.fetchAvailableSurveys();
        expect(fetchedSurveys.length, 1,
            reason: 'The snooze period has elapsed');
      });
    });

    test('Dimissing permanently is successful', () async {
      final minutesToSnooze = 10;
      final surveyToLoad = Survey(
        uniqueId: 'uniqueId',
        startDate: DateTime(2023, 1, 1),
        endDate: DateTime(2023, 12, 31),
        description: 'description',
        snoozeForMinutes: minutesToSnooze,
        samplingRate: 1.0,
        excludeDashToolList: [],
        conditionList: <Condition>[],
        buttonList: [
          SurveyButton(
            buttonText: 'buttonText',
            action: 'accept',
            promptRemainsVisible: false,
          ),
          SurveyButton(
            buttonText: 'buttonText',
            action: 'dismiss',
            promptRemainsVisible: false,
          ),
        ],
      );

      await withClock(Clock.fixed(DateTime(2023, 3, 3, 12, 0)), () async {
        analytics = Analytics.fake(
          tool: DashTool.flutterTool,
          homeDirectory: homeDirectory,
          dartVersion: 'dartVersion',
          fs: fs,
          platform: DevicePlatform.macos,
          surveyHandler: FakeSurveyHandler.fromList(
            dismissedSurveyFile: dismissedSurveyFile,
            initializedSurveys: <Survey>[surveyToLoad],
          ),
        );

        final fetchedSurveys = await analytics.fetchAvailableSurveys();
        expect(fetchedSurveys.length, 1);

        // Dismissing permanently will ensure that this survey is not
        // shown again
        final survey = fetchedSurveys.first;
        analytics.surveyInteracted(
          survey: survey,
          surveyButton: survey.buttonList.first,
        );
      });

      // Moving out a week
      await withClock(Clock.fixed(DateTime(2023, 3, 10, 12, 0)), () async {
        analytics = Analytics.fake(
          tool: DashTool.flutterTool,
          homeDirectory: homeDirectory,
          dartVersion: 'dartVersion',
          fs: fs,
          platform: DevicePlatform.macos,
          surveyHandler: FakeSurveyHandler.fromList(
            dismissedSurveyFile: dismissedSurveyFile,
            initializedSurveys: <Survey>[surveyToLoad],
          ),
        );

        final fetchedSurveys = await analytics.fetchAvailableSurveys();
        expect(fetchedSurveys.length, 0);
      });
    });

    test('malformed persisted json file for surveys', () async {
      // When the survey handler encounters an error when parsing the
      // persisted json file, it will reset it using the static method
      // under the [Initializer] class and reset it to be an empty json object
      final minutesToSnooze = 10;
      final surveyToLoad = Survey(
        uniqueId: 'uniqueId',
        startDate: DateTime(2023, 1, 1),
        endDate: DateTime(2023, 12, 31),
        description: 'description',
        snoozeForMinutes: minutesToSnooze,
        samplingRate: 1.0,
        excludeDashToolList: [],
        conditionList: <Condition>[],
        buttonList: [
          SurveyButton(
            buttonText: 'buttonText',
            action: 'accept',
            promptRemainsVisible: false,
          ),
          SurveyButton(
            buttonText: 'buttonText',
            action: 'dismiss',
            promptRemainsVisible: false,
          ),
        ],
      );

      await withClock(Clock.fixed(DateTime(2023, 3, 3, 12, 0)), () async {
        analytics = Analytics.fake(
          tool: DashTool.flutterTool,
          homeDirectory: homeDirectory,
          dartVersion: 'dartVersion',
          fs: fs,
          platform: DevicePlatform.macos,
          surveyHandler: FakeSurveyHandler.fromList(
            dismissedSurveyFile: dismissedSurveyFile,
            initializedSurveys: <Survey>[surveyToLoad],
          ),
        );

        final fetchedSurveys = await analytics.fetchAvailableSurveys();
        expect(fetchedSurveys.length, 1);

        // Dismissing permanently will ensure that this survey is not
        // shown again
        final survey = fetchedSurveys.first;
        expect(survey.buttonList.length, 2);
        analytics.surveyInteracted(
          survey: survey,
          surveyButton: survey.buttonList.first,
        );
      });

      // Purposefully write invalid json into the persisted file
      dismissedSurveyFile.writeAsStringSync('{');

      // Moving out a week
      await withClock(Clock.fixed(DateTime(2023, 3, 10, 12, 0)), () async {
        analytics = Analytics.fake(
          tool: DashTool.flutterTool,
          homeDirectory: homeDirectory,
          dartVersion: 'dartVersion',
          fs: fs,
          platform: DevicePlatform.macos,
          surveyHandler: FakeSurveyHandler.fromList(
            dismissedSurveyFile: dismissedSurveyFile,
            initializedSurveys: <Survey>[surveyToLoad],
          ),
        );

        final fetchedSurveys = await analytics.fetchAvailableSurveys();
        expect(fetchedSurveys.length, 1);
      });
    });

    test('persisted json file goes missing handled', () async {
      // If the persisted json file with the dismissed surveys is missing
      // there should be error handling to recreate the file again with
      // an empty json object
      final minutesToSnooze = 10;
      final surveyToLoad = Survey(
        uniqueId: 'uniqueId',
        startDate: DateTime(2023, 1, 1),
        endDate: DateTime(2023, 12, 31),
        description: 'description',
        snoozeForMinutes: minutesToSnooze,
        samplingRate: 1.0,
        excludeDashToolList: [],
        conditionList: <Condition>[],
        buttonList: [
          SurveyButton(
            buttonText: 'buttonText',
            action: 'accept',
            promptRemainsVisible: false,
          ),
          SurveyButton(
            buttonText: 'buttonText',
            action: 'dismiss',
            promptRemainsVisible: false,
          ),
        ],
      );

      await withClock(Clock.fixed(DateTime(2023, 3, 3, 12, 0)), () async {
        analytics = Analytics.fake(
          tool: DashTool.flutterTool,
          homeDirectory: homeDirectory,
          dartVersion: 'dartVersion',
          fs: fs,
          platform: DevicePlatform.macos,
          surveyHandler: FakeSurveyHandler.fromList(
            dismissedSurveyFile: dismissedSurveyFile,
            initializedSurveys: <Survey>[surveyToLoad],
          ),
        );

        final fetchedSurveys = await analytics.fetchAvailableSurveys();
        expect(fetchedSurveys.length, 1);

        // Dismissing permanently will ensure that this survey is not
        // shown again
        final survey = fetchedSurveys.first;
        expect(survey.buttonList.length, 2);
        analytics.surveyInteracted(
          survey: survey,
          surveyButton: survey.buttonList.first,
        );
      });

      // Moving out a week
      await withClock(Clock.fixed(DateTime(2023, 3, 10, 12, 0)), () async {
        analytics = Analytics.fake(
          tool: DashTool.flutterTool,
          homeDirectory: homeDirectory,
          dartVersion: 'dartVersion',
          fs: fs,
          platform: DevicePlatform.macos,
          surveyHandler: FakeSurveyHandler.fromList(
            dismissedSurveyFile: dismissedSurveyFile,
            initializedSurveys: <Survey>[surveyToLoad],
          ),
        );

        // Purposefully delete the file
        dismissedSurveyFile.deleteSync();

        final fetchedSurveys = await analytics.fetchAvailableSurveys();
        expect(fetchedSurveys.length, 1);
      });
    });

    test('Filtering out with excludeDashTool array', () async {
      await withClock(Clock.fixed(DateTime(2023, 3, 3)), () async {
        analytics = Analytics.fake(
          tool: DashTool.flutterTool,
          homeDirectory: homeDirectory,
          dartVersion: 'dartVersion',
          fs: fs,
          platform: DevicePlatform.macos,
          surveyHandler: FakeSurveyHandler.fromList(
            dismissedSurveyFile: dismissedSurveyFile,
            initializedSurveys: <Survey>[
              Survey(
                uniqueId: 'uniqueId',
                startDate: DateTime(2023, 1, 1),
                endDate: DateTime(2023, 12, 31),
                description: 'description',
                snoozeForMinutes: 10,
                samplingRate: 1.0,
                // This should be the same as the tool in the
                // Analytics constructor above
                excludeDashToolList: [
                  DashTool.flutterTool,
                ],
                conditionList: [],
                buttonList: [
                  SurveyButton(
                    buttonText: 'buttonText',
                    action: 'accept',
                    url: 'http://example.com',
                    promptRemainsVisible: false,
                  ),
                ],
              ),
            ],
          ),
        );

        final fetchedSurveys = await analytics.fetchAvailableSurveys();

        expect(fetchedSurveys.length, 0);
      });
    });

    test(
        'Filter from excludeDashTool array does not '
        'apply for different tool', () async {
      await withClock(Clock.fixed(DateTime(2023, 3, 3)), () async {
        analytics = Analytics.fake(
          tool: DashTool.flutterTool,
          homeDirectory: homeDirectory,
          dartVersion: 'dartVersion',
          fs: fs,
          platform: DevicePlatform.macos,
          surveyHandler: FakeSurveyHandler.fromList(
            dismissedSurveyFile: dismissedSurveyFile,
            initializedSurveys: <Survey>[
              Survey(
                uniqueId: 'uniqueId',
                startDate: DateTime(2023, 1, 1),
                endDate: DateTime(2023, 12, 31),
                description: 'description',
                snoozeForMinutes: 10,
                samplingRate: 1.0,
                // This should be different from the tool in the
                // Analytics constructor above
                excludeDashToolList: [
                  DashTool.devtools,
                ],
                conditionList: [],
                buttonList: [
                  SurveyButton(
                    buttonText: 'buttonText',
                    action: 'accept',
                    url: 'http://example.com',
                    promptRemainsVisible: false,
                  ),
                ],
              ),
            ],
          ),
        );

        final fetchedSurveys = await analytics.fetchAvailableSurveys();

        expect(fetchedSurveys.length, 1);

        final survey = fetchedSurveys.first;
        expect(survey.excludeDashToolList.length, 1);
        expect(survey.excludeDashToolList.contains(DashTool.devtools), true);
      });
    });
  });
}
