// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "base/strings/sys_string_conversions.h"
#import "base/test/metrics/histogram_tester.h"
#import "ios/chrome/common/app_group/app_group_constants.h"
#import "ios/chrome/common/app_group/app_group_metrics_mainapp.h"
#import "testing/gtest_mac.h"
#import "testing/platform_test.h"

using app_group::KeyForOpenExtensionOutcomeType;
using app_group::kOpenExtensionOutcomeFailureInvalidURL;
using app_group::kOpenExtensionOutcomeFailureOpenInNotFound;
using app_group::kOpenExtensionOutcomeFailureUnsupportedScheme;
using app_group::kOpenExtensionOutcomeFailureURLNotFound;
using app_group::kOpenExtensionOutcomes;
using app_group::kOpenExtensionOutcomeSuccess;
using app_group::OpenExtensionOutcome;
using app_group::OutcomeTypeFromKey;
using app_group::main_app::LogOpenExtensionMetrics;

class AppGroupMetricsTest : public PlatformTest {
 protected:
  void TearDown() override { PlatformTest::TearDown(); }
};

// Verify that the input key is converted correctly to OpenExtensionOutcome.
TEST_F(AppGroupMetricsTest, TestKeyForOpenExtensionOutcomeType) {
  EXPECT_EQ(KeyForOpenExtensionOutcomeType(OpenExtensionOutcome::kSuccess),
            kOpenExtensionOutcomeSuccess);

  EXPECT_EQ(
      KeyForOpenExtensionOutcomeType(OpenExtensionOutcome::kFailureInvalidURL),
      kOpenExtensionOutcomeFailureInvalidURL);

  EXPECT_EQ(
      KeyForOpenExtensionOutcomeType(OpenExtensionOutcome::kFailureURLNotFound),
      kOpenExtensionOutcomeFailureURLNotFound);

  EXPECT_EQ(KeyForOpenExtensionOutcomeType(
                OpenExtensionOutcome::kFailureOpenInNotFound),
            kOpenExtensionOutcomeFailureOpenInNotFound);

  EXPECT_EQ(KeyForOpenExtensionOutcomeType(
                OpenExtensionOutcome::kFailureUnsupportedScheme),
            kOpenExtensionOutcomeFailureUnsupportedScheme);
}

// Verify that the OpenExtensionOutcome is converted correctly to
// OpenExtensionOutcome enum key.
TEST_F(AppGroupMetricsTest, TestOutcomeTypeFromKey) {
  EXPECT_EQ(OutcomeTypeFromKey(kOpenExtensionOutcomeSuccess),
            OpenExtensionOutcome::kSuccess);

  EXPECT_EQ(OutcomeTypeFromKey(kOpenExtensionOutcomeFailureInvalidURL),
            OpenExtensionOutcome::kFailureInvalidURL);

  EXPECT_EQ(OutcomeTypeFromKey(kOpenExtensionOutcomeFailureURLNotFound),
            OpenExtensionOutcome::kFailureURLNotFound);

  EXPECT_EQ(OutcomeTypeFromKey(kOpenExtensionOutcomeFailureOpenInNotFound),
            OpenExtensionOutcome::kFailureOpenInNotFound);

  EXPECT_EQ(OutcomeTypeFromKey(kOpenExtensionOutcomeFailureUnsupportedScheme),
            OpenExtensionOutcome::kFailureUnsupportedScheme);

  EXPECT_EQ(OutcomeTypeFromKey(@"OpenExtensionOutcomeURL"),
            OpenExtensionOutcome::kInvalid);
}

// Verify that the open extension metrics are logged correctly and
// cleaned from the UserDefaults.
TEST_F(AppGroupMetricsTest, TestLogOpenExtensionMetrics) {
  base::HistogramTester histogram_tester;

  NSDictionary<NSString*, NSNumber*>* open_extension_test_dictionnary = @{
    kOpenExtensionOutcomeSuccess : @1,
    kOpenExtensionOutcomeFailureInvalidURL : @2,
    kOpenExtensionOutcomeFailureURLNotFound : @3,
    kOpenExtensionOutcomeFailureOpenInNotFound : @4,
    kOpenExtensionOutcomeFailureUnsupportedScheme : @5,
    @"InvalidString" : @6
  };

  NSUserDefaults* shared_defaults = app_group::GetGroupUserDefaults();

  [shared_defaults setObject:open_extension_test_dictionnary
                      forKey:kOpenExtensionOutcomes];

  LogOpenExtensionMetrics();

  // Verify that the total of events is registered.
  histogram_tester.ExpectTotalCount("IOSOpenExtensionOutcome", 21);

  // Verify that the total of each bucket contains the right number of events.
  histogram_tester.ExpectBucketCount("IOSOpenExtensionOutcome",
                                     OpenExtensionOutcome::kSuccess, 1);
  histogram_tester.ExpectBucketCount(
      "IOSOpenExtensionOutcome", OpenExtensionOutcome::kFailureInvalidURL, 2);
  histogram_tester.ExpectBucketCount(
      "IOSOpenExtensionOutcome", OpenExtensionOutcome::kFailureURLNotFound, 3);
  histogram_tester.ExpectBucketCount(
      "IOSOpenExtensionOutcome", OpenExtensionOutcome::kFailureOpenInNotFound,
      4);
  histogram_tester.ExpectBucketCount(
      "IOSOpenExtensionOutcome",
      OpenExtensionOutcome::kFailureUnsupportedScheme, 5);
  histogram_tester.ExpectBucketCount("IOSOpenExtensionOutcome",
                                     OpenExtensionOutcome::kInvalid, 6);

  EXPECT_FALSE([shared_defaults objectForKey:kOpenExtensionOutcomes]);
}

// Verify dictionary values cases for NSNumber and
// that the total of each bucket is less/equal 10.
TEST_F(AppGroupMetricsTest, TestValuesCasesForLogOpenExtensionMetrics) {
  base::HistogramTester histogram_tester;
  // NSNumber Test cases
  // 1 bucket with value 100
  // 1 bucket with negative value
  // 1 bucket with decimal value
  // 1 bucket with [NSDecimalNumber notANumber]
  // 1 bucket with something that is not a number (string)*/
  NSDictionary* open_extension_test_dictionnary = @{
    kOpenExtensionOutcomeSuccess : @100,
    kOpenExtensionOutcomeFailureInvalidURL : @-6,
    kOpenExtensionOutcomeFailureURLNotFound : @3.2,
    kOpenExtensionOutcomeFailureOpenInNotFound : NSDecimalNumber.notANumber,
    kOpenExtensionOutcomeFailureUnsupportedScheme : @"str",
    @"InvalidString" : @10
  };

  NSUserDefaults* shared_defaults = app_group::GetGroupUserDefaults();

  [shared_defaults setObject:open_extension_test_dictionnary
                      forKey:kOpenExtensionOutcomes];

  LogOpenExtensionMetrics();

  // Verify that the total of events is registered.
  histogram_tester.ExpectTotalCount("IOSOpenExtensionOutcome", 23);

  // Verify that the total of each bucket contains the right number of events.
  histogram_tester.ExpectBucketCount("IOSOpenExtensionOutcome",
                                     OpenExtensionOutcome::kSuccess, 10);
  histogram_tester.ExpectBucketCount(
      "IOSOpenExtensionOutcome", OpenExtensionOutcome::kFailureInvalidURL, 0);
  histogram_tester.ExpectBucketCount(
      "IOSOpenExtensionOutcome", OpenExtensionOutcome::kFailureURLNotFound, 3);
  histogram_tester.ExpectBucketCount(
      "IOSOpenExtensionOutcome", OpenExtensionOutcome::kFailureOpenInNotFound,
      0);
  histogram_tester.ExpectBucketCount(
      "IOSOpenExtensionOutcome",
      OpenExtensionOutcome::kFailureUnsupportedScheme, 0);
  histogram_tester.ExpectBucketCount("IOSOpenExtensionOutcome",
                                     OpenExtensionOutcome::kInvalid, 10);

  EXPECT_FALSE([shared_defaults objectForKey:kOpenExtensionOutcomes]);
}

// Verify the case of when there's no dictionary.
TEST_F(AppGroupMetricsTest, TestLogOpenExtensionMetricsMissingDictionary) {
  base::HistogramTester histogram_tester;

  LogOpenExtensionMetrics();

  histogram_tester.ExpectTotalCount("IOSOpenExtensionOutcome", 0);
}

// Verify that the open extension metrics are logged correctly with
// missing buckets and cleaned from the UserDefaults.
TEST_F(AppGroupMetricsTest, TestLogOpenExtensionMetricsWithMissingBuckets) {
  base::HistogramTester histogram_tester;

  NSDictionary<NSString*, NSNumber*>* open_extension_test_dictionnary = @{
    kOpenExtensionOutcomeSuccess : @5,
    kOpenExtensionOutcomeFailureInvalidURL : @10,
    kOpenExtensionOutcomeFailureURLNotFound : @3,
  };

  NSUserDefaults* shared_defaults = app_group::GetGroupUserDefaults();

  [shared_defaults setObject:open_extension_test_dictionnary
                      forKey:kOpenExtensionOutcomes];

  LogOpenExtensionMetrics();

  histogram_tester.ExpectTotalCount("IOSOpenExtensionOutcome", 18);

  EXPECT_FALSE([shared_defaults objectForKey:kOpenExtensionOutcomes]);
}

// Verify that key starting with '@' (potential KVC injection payload) is
// processed safely.
TEST_F(AppGroupMetricsTest, TestLogOpenExtensionMetricsWithKVCInjectionKey) {
  base::HistogramTester histogram_tester;

  NSDictionary<NSString*, NSNumber*>* open_extension_test_dictionary = @{
    @"@dealloc" : @5,
    kOpenExtensionOutcomeSuccess : @3,
  };

  NSUserDefaults* shared_defaults = app_group::GetGroupUserDefaults();

  [shared_defaults setObject:open_extension_test_dictionary
                      forKey:kOpenExtensionOutcomes];

  LogOpenExtensionMetrics();

  histogram_tester.ExpectTotalCount("IOSOpenExtensionOutcome", 8);

  histogram_tester.ExpectBucketCount("IOSOpenExtensionOutcome",
                                     OpenExtensionOutcome::kInvalid, 5);
  histogram_tester.ExpectBucketCount("IOSOpenExtensionOutcome",
                                     OpenExtensionOutcome::kSuccess, 3);

  EXPECT_FALSE([shared_defaults objectForKey:kOpenExtensionOutcomes]);
}
