// Copyright 2018 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/command_line.h"
#import "base/files/file_path.h"
#import "base/files/file_util.h"
#import "base/json/json_reader.h"
#import "base/strings/sys_string_conversions.h"
#import "base/threading/thread_restrictions.h"
#import "base/values.h"
#import "components/autofill/core/browser/field_types.h"
#import "components/autofill/core/common/autofill_debug_features.h"
#import "components/autofill/core/common/autofill_features.h"
#import "ios/chrome/browser/autofill/model/automation/automation_action.h"
#import "ios/chrome/browser/autofill/model/automation/automation_app_interface.h"
#import "ios/chrome/test/earl_grey/chrome_earl_grey.h"
#import "ios/chrome/test/earl_grey/chrome_test_case.h"
#import "ios/testing/earl_grey/app_launch_configuration.h"
#import "ios/testing/earl_grey/earl_grey_test.h"

namespace {

static const char kAutofillAutomationSwitch[] = "autofillautomation";

// Private helper method for accessing app interface method.
NSError* SetAutofillAutomationProfile(const std::string& profile_json_string) {
  NSString* profile_json_nsstring =
      base::SysUTF8ToNSString(profile_json_string);
  return [AutomationAppInterface
      setAutofillAutomationProfile:profile_json_nsstring];
}

// Loads the recipe file and reads it into std::string.
std::string ReadRecipeJsonFromPath(const base::FilePath& path) {
  base::ScopedAllowBlockingForTesting allow_blocking;
  std::string json_text;
  bool read_success = base::ReadFileToString(path, &json_text);
  GREYAssert(read_success, @"Unable to read JSON file.");
  return json_text;
}

// Parses recipe std::string into base::Value.
base::Value RecipeJsonToValue(const std::string& recipe_json) {
  std::optional<base::Value> value =
      base::JSONReader::Read(recipe_json, base::JSON_PARSE_CHROMIUM_EXTENSIONS);
  GREYAssert(value.has_value(), @"Unable to parse JSON string.");
  GREYAssert(value.value().is_dict(),
             @"Expecting a dictionary in the recipe JSON string.");
  return std::move(value).value();
}

}  // namespace

// The autofill automation test case is intended to run a script against a
// captured web site. It gets the script from the command line.
@interface AutofillAutomationTestCase : ChromeTestCase {
  bool _shouldRecordException;
  GURL _startURL;
  NSMutableArray<AutomationAction*>* _actions;
}
@end

@implementation AutofillAutomationTestCase

// Retrieves the path to the recipe file from the command line.
+ (const base::FilePath)recipePath {
  base::ScopedAllowBlockingForTesting allow_blocking;
  base::CommandLine* commandLine(base::CommandLine::ForCurrentProcess());
  GREYAssert(commandLine->HasSwitch(kAutofillAutomationSwitch),
             @"Missing command line switch %s.", kAutofillAutomationSwitch);

  base::FilePath path(
      commandLine->GetSwitchValuePath(kAutofillAutomationSwitch));
  GREYAssert(!path.empty(),
             @"A file name must be specified for command line switch %s.",
             kAutofillAutomationSwitch);
  GREYAssert(path.IsAbsolute(),
             @"A fully qualified file name must be specified for command "
             @"line switch %s.",
             kAutofillAutomationSwitch);
  GREYAssert(base::PathExists(path), @"File not found for switch %s.",
             kAutofillAutomationSwitch);

  return path;
}

- (AppLaunchConfiguration)appConfigurationForTestCase {
  AppLaunchConfiguration config;
  config.features_enabled.push_back(
      autofill::features::debug::kAutofillShowTypePredictions);
  return config;
}

- (void)setUp {
  self->_shouldRecordException = true;

  [super setUp];

  const base::FilePath recipePath = [[self class] recipePath];
  std::string recipeJSONText = ReadRecipeJsonFromPath(recipePath);
  base::Value recipeRoot = RecipeJsonToValue(recipeJSONText);

  NSError* error = SetAutofillAutomationProfile(recipeJSONText);
  chrome_test_util::GREYAssertErrorNil(error);

  // Extract the starting URL.
  std::string* startURLValue = recipeRoot.GetDict().FindString("startingURL");
  GREYAssert(startURLValue, @"Test file is missing startingURL.");

  const std::string startURLString(*startURLValue);
  GREYAssert(!startURLString.empty(), @"startingURL is an empty value.");

  _startURL = GURL(startURLString);

  // Extract the actions.
  base::ListValue* actions = recipeRoot.GetDict().FindList("actions");
  GREYAssert(actions, @"Test file is missing actions.");
  GREYAssert(!actions->empty(), @"Test file has empty actions.");

  _actions = [[NSMutableArray alloc] initWithCapacity:actions->size()];
  for (base::Value& action : *actions) {
    GREYAssert(action.is_dict(), @"Expecting each action to be a ...");
    [_actions addObject:[AutomationAction
                            actionWithValueDict:std::move(action.GetDict())]];
  }
}

// Runs the recipe provided.
- (void)testActions {
  [ChromeEarlGrey loadURL:_startURL];

  for (AutomationAction* action in _actions) {
    [action execute];
  }
}

@end
