# Copyright 2016 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Create Android test matrices in Firebase Test Lab."""

import os
import uuid

from apitools.base.py import exceptions as apitools_exceptions

from googlecloudsdk.api_lib.test import matrix_ops
from googlecloudsdk.api_lib.test import util
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.core import log


def CreateMatrix(args, context, history_id, gcs_results_root):
  """Creates a new matrix test in Firebase Test Lab from the user's params.

  Args:
    args: an argparse namespace. All the arguments that were provided to this
      gcloud command invocation (i.e. group and command arguments combined).
    context: {str:obj} dict containing the gcloud command context, which
      includes the Testing API client+messages libs generated by Apitools.
    history_id: {str} A history ID to publish Tool Results to.
    gcs_results_root: the root dir for a matrix within the GCS results bucket.

  Returns:
    A TestMatrix object created from the supplied matrix configuration values.
  """
  creator = MatrixCreator(args, context, history_id, gcs_results_root)
  return creator.CreateTestMatrix(uuid.uuid4().hex)


class MatrixCreator(object):
  """Creates a single test matrix based on user-supplied test arguments."""

  def __init__(self, args, context, history_id, gcs_results_root):
    """Construct a MatrixCreator to be used to create a single test matrix.

    Args:
      args: an argparse namespace. All the arguments that were provided to this
        gcloud command invocation (i.e. group and command arguments combined).
      context: {str:obj} dict containing the gcloud command context, which
        includes the Testing API client+messages libs generated by Apitools.
      history_id: {str} A history ID to publish Tool Results to.
      gcs_results_root: the root dir for a matrix within the GCS results bucket.
    """
    self._project = util.GetProject()
    self._args = args
    self._history_id = history_id
    self._gcs_results_root = gcs_results_root
    self._client = context['testing_client']
    self._messages = context['testing_messages']

  def _BuildFileReference(self, filename):
    """Build a FileReference pointing to the GCS copy of an APK/OBB file."""
    return self._messages.FileReference(
        gcsPath=os.path.join(self._gcs_results_root,
                             os.path.basename(filename)))

  def _BuildAndroidInstrumentationTestSpec(self):
    """Build a TestSpecification for an AndroidInstrumentationTest."""
    spec = self._BuildGenericTestSpec()
    spec.androidInstrumentationTest = self._messages.AndroidInstrumentationTest(
        appApk=self._BuildFileReference(self._args.app),
        testApk=self._BuildFileReference(self._args.test),
        appPackageId=self._args.app_package,
        testPackageId=self._args.test_package,
        testRunnerClass=self._args.test_runner_class,
        testTargets=(self._args.test_targets or []))
    return spec

  def _BuildAndroidRoboTestSpec(self):
    """Build a TestSpecification for an AndroidRoboTest."""
    spec = self._BuildGenericTestSpec()

    robo_directives = []
    if self._args.robo_directives:
      for key, value in self._args.robo_directives.iteritems():
        robo_directives.append(
            self._messages.RoboDirective(resourceName=key, inputText=value))

    spec.androidRoboTest = self._messages.AndroidRoboTest(
        appApk=self._BuildFileReference(self._args.app),
        appPackageId=self._args.app_package,
        maxDepth=self._args.max_depth,
        maxSteps=self._args.max_steps,
        appInitialActivity=self._args.app_initial_activity,
        roboDirectives=robo_directives)
    return spec

  def _BuildGenericTestSpec(self):
    """Build a generic TestSpecification without test-type specifics."""
    device_files = []
    if self._args.obb_files:
      for obb_file in self._args.obb_files:
        device_files.append(self._messages.DeviceFile(
            obbFile=self._messages.ObbFile(
                obbFileName=os.path.basename(obb_file),
                obb=self._BuildFileReference(obb_file))))

    environment_variables = []
    if self._args.environment_variables:
      for key, value in self._args.environment_variables.iteritems():
        environment_variables.append(
            self._messages.EnvironmentVariable(
                key=key, value=value))

    directories_to_pull = self._args.directories_to_pull or []

    account = None
    if self._args.auto_google_login:
      account = self._messages.Account(googleAuto=self._messages.GoogleAuto())

    setup = self._messages.TestSetup(
        filesToPush=device_files,
        account=account,
        environmentVariables=environment_variables,
        directoriesToPull=directories_to_pull)

    return self._messages.TestSpecification(
        testTimeout=matrix_ops.ReformatDuration(self._args.timeout),
        testSetup=setup)

  def _TestSpecFromType(self, test_type):
    """Map a test type into its corresponding TestSpecification message ."""
    if test_type == 'instrumentation':
      return self._BuildAndroidInstrumentationTestSpec()
    elif test_type == 'robo':
      return self._BuildAndroidRoboTestSpec()
    else:  # It's a bug in our arg validation if we ever get here.
      raise exceptions.InvalidArgumentException(
          'type', 'Unknown test type "{}".'.format(test_type))

  def _BuildTestMatrix(self, spec):
    """Build just the user-specified parts of a TestMatrix message.

    Args:
      spec: a TestSpecification message corresponding to the test type.

    Returns:
      A TestMatrix message.
    """
    android_matrix = self._messages.AndroidMatrix(
        androidModelIds=self._args.device_ids,
        androidVersionIds=self._args.os_version_ids,
        locales=self._args.locales,
        orientations=self._args.orientations)

    gcs = self._messages.GoogleCloudStorage(gcsPath=self._gcs_results_root)
    hist = self._messages.ToolResultsHistory(projectId=self._project,
                                             historyId=self._history_id)
    results = self._messages.ResultStorage(googleCloudStorage=gcs,
                                           toolResultsHistory=hist)

    return self._messages.TestMatrix(
        testSpecification=spec,
        environmentMatrix=self._messages.EnvironmentMatrix(
            androidMatrix=android_matrix),
        clientInfo=self._messages.ClientInfo(name='gcloud'),
        resultStorage=results)

  def _BuildTestMatrixRequest(self, request_id):
    """Build a TestingProjectsTestMatricesCreateRequest for a test matrix.

    Args:
      request_id: {str} a unique ID for the CreateTestMatrixRequest.

    Returns:
      A TestingProjectsTestMatricesCreateRequest message.
    """
    spec = self._TestSpecFromType(self._args.type)
    return self._messages.TestingProjectsTestMatricesCreateRequest(
        projectId=self._project,
        testMatrix=self._BuildTestMatrix(spec),
        requestId=request_id)

  def CreateTestMatrix(self, request_id):
    """Invoke the Testing service to create a test matrix from the user's args.

    Args:
      request_id: {str} a unique ID for the CreateTestMatrixRequest.

    Returns:
      The TestMatrix response message from the TestMatrices.Create rpc.

    Raises:
      HttpException if the test service reports an HttpError.
    """
    request = self._BuildTestMatrixRequest(request_id)
    log.debug('TestMatrices.Create request:\n{0}\n'.format(request))
    try:
      response = self._client.projects_testMatrices.Create(request)
      log.debug('TestMatrices.Create response:\n{0}\n'.format(response))
    except apitools_exceptions.HttpError as error:
      msg = 'Http error while creating test matrix: ' + util.GetError(error)
      raise exceptions.HttpException(msg)

    log.status.Print('Test [{id}] has been created in the Google Cloud.'
                     .format(id=response.testMatrixId))
    return response
