"""Command-line test runner script for running Bluetooth tests.

This module allows users to initiate Bluetooth test targets and run them against
specified DUTs (devices-under-test) using a simple command line interface.
"""

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import base64

from absl import app
from absl import flags
from absl import logging

# Internal import
# Internal import
# Internal import
# Internal import
# Internal import

FLAGS = flags.FLAGS

flags.DEFINE_multi_string('bt_test', None, 'Bluetooth test to run.')
flags.DEFINE_multi_string('bt_dut', None, 'Bluetooth device to allocate for tests.')

# Valid config keys for the --bt_test command line flag.
BT_TEST_CONFIG_KEYS = {'target'}

# Valid config keys for the --bt_dut (device-under-test) command line flag.
BT_DUT_CONFIG_KEYS = {'hardware'}

TEST_FLAGS = ('--notest_loasd --test_output=streamed '
              '--test_arg=--param_dut_config=%s')


class Error(Exception):
    """Base class for module exceptions."""
    pass


class TestConfigError(Error):
    """Raised when --bt_test config flags are specified incorrectly."""
    pass


class DutConfigError(Error):
    """Raised when --bt_dut config flags are specified incorrectly."""
    pass


def validate_bt_test_flags(flag_value):
    """Validates the format of specified --bt_test flags.

  Args:
    flag_value: string, the config flag value for a given --bt_test flag.

  Returns:
    bool, True if --bt_test flags have been specified correctly.
  """
    if not flag_value:
        logging.error('No tests specified! Please specify at least one '
                      'test using the --bt_test flag.')
        return False
    for test in flag_value:
        config_args = test.split(',')
        for config_arg in config_args:
            if config_arg.split('=')[0] not in BT_TEST_CONFIG_KEYS:
                logging.error('--bt_test config key "%s" is invalid!', config_arg.split('=')[0])
                return False
    return True


def validate_bt_dut_flags(flag_value):
    """Validates the format of specified --bt_dut flags.

  Args:
    flag_value: string, the config flag value for a given --bt_dut flag.

  Returns:
    bool, True if --bt_dut flags have been specified correctly.
  """
    if not flag_value:
        logging.error('No DUTs specified! Please specify at least one '
                      'DUT using the --bt_dut flag.')
        return False
    for dut in flag_value:
        config_args = dut.split(',')
        for config_arg in config_args:
            if config_arg.split('=')[0] not in BT_DUT_CONFIG_KEYS:
                logging.error('--bt_dut config key "%s" is invalid!', config_arg.split('=')[0])
                return False
    return True


flags.register_validator('bt_test', validate_bt_test_flags,
                         ('Invalid --bt_test configuration specified!'
                          ' Valid configuration fields include: %s') % BT_TEST_CONFIG_KEYS)

flags.register_validator('bt_dut', validate_bt_dut_flags,
                         ('Invalid --bt_dut configuration specified!'
                          ' Valid configuration fields include: %s') % BT_DUT_CONFIG_KEYS)


def parse_flag_value(flag_value):
    """Parses a config flag value string into a dict.

  Example input: 'target=//tests:bluetooth_pairing_test'
  Example output: {'target': '//tests:bluetooth_pairing_test'}

  Args:
    flag_value: string, the config flag value for a given flag.

  Returns:
    dict, A dict object representation of a config flag value.
  """
    config_dict = {}
    config_args = flag_value.split(',')
    for config_arg in config_args:
        config_dict[config_arg.split('=')[0]] = config_arg.split('=')[1]
    return config_dict


def get_device_type(gateway_stub, dut_config_dict):
    """Determines a device type based on a device query.

  Args:
    gateway_stub: An RPC2 stub object.
    dut_config_dict: dict, A dict of device config args.

  Returns:
    string, The MobileHarness device type.

  Raises:
    DutConfigError: If --bt_dut flag(s) are incorrectly specified.
  """
    device_query_filter = device_query_pb2.DeviceQueryFilter()
    device_query_filter.type_regex.append('AndroidRealDevice')
    for dut_config_key in dut_config_dict:
        dimension_filter = device_query_filter.dimension_filter.add()
        dimension_filter.name = dut_config_key
        dimension_filter.value_regex = dut_config_dict[dut_config_key]
    request = gateway_service_pb2.QueryDeviceRequest(device_query_filter=device_query_filter)
    response = gateway_stub.QueryDevice(request)
    if response.device_query_result.device_info:
        return 'AndroidRealDevice'

    device_query_filter.ClearField('type_regex')
    device_query_filter.type_regex.append('TestbedDevice')
    request = gateway_service_pb2.QueryDeviceRequest(device_query_filter=device_query_filter)
    response = gateway_stub.QueryDevice(request)
    if response.device_query_result.device_info:
        return 'TestbedDevice'

    raise DutConfigError('Invalid --bt_dut config specified: %s' % dut_config_dict)


def generate_dut_configs(gateway_stub):
    """Generates a unicode string specifying the desired DUT configurations.

  Args:
    gateway_stub: An RPC2 stub object.

  Returns:
    string, Unicode string specifying DUT configurations.

  Raises:
    DutConfigError: If --bt_dut flag(s) are incorrectly specified.
  """
    dut_list = job_config_pb2.JobConfig().DeviceList()
    dut_config_dict_list = [parse_flag_value(value) for value in FLAGS.bt_dut]

    for dut_config_dict in dut_config_dict_list:
        dut_config_dict['pool'] = 'bluetooth-iop'
        dut = job_config_pb2.JobConfig().SubDeviceSpec()
        if 'hardware' not in dut_config_dict:
            raise DutConfigError('Must specify hardware name for bt_dut: %s' % dut_config_dict)
        dut.type = get_device_type(gateway_stub, dut_config_dict)
        for config_key in dut_config_dict:
            dut.dimensions.content[config_key] = dut_config_dict[config_key]
        dut_list.sub_device_spec.append(dut)
    logging.info(base64.b64encode(dut_list.SerializeToString()).decode('utf-8'))
    return base64.b64encode(dut_list.SerializeToString()).decode('utf-8')


def generate_blaze_targets(session_config, gateway_stub):
    """Generates and appends blaze test targets to a MobileHarness session.

  Args:
    session_config: The SessionConfig object to append blaze test targets to.
    gateway_stub: An RPC2 stub object.

  Raises:
     TestConfigError: If --bt_test flag(s) are incorrectly specified.
  """
    test_config_dict_list = [parse_flag_value(value) for value in FLAGS.bt_test]

    for test_config_dict in test_config_dict_list:
        target = setting_pb2.BlazeTarget()
        if 'target' not in test_config_dict:
            raise TestConfigError('Must specify a target for bt_test: %s' % test_config_dict)
        target.target_name = test_config_dict['target']
        target.test_flags = TEST_FLAGS % generate_dut_configs(gateway_stub)
        session_config.blaze_target.append(target)


def run_session():
    """Runs a configured test session.

  Returns:
    A RunSessionResponse object.
  """
    session_config = setting_pb2.SessionConfig()
    channel = rpcutil.GetNewChannel('blade:mobileharness-gateway')
    gateway_stub = gateway_service_pb2.GatewayService.NewRPC2Stub(channel=channel)
    generate_blaze_targets(session_config, gateway_stub)
    request = gateway_service_pb2.RunSessionRequest()
    request.session_config.CopyFrom(session_config)
    response = gateway_stub.RunSession(request)
    logging.info('Sponge link: %s', response.sponge)
    logging.info('Session ID: %s', response.session_id)
    return response


def main(argv):
    logging.use_python_logging()
    del argv
    run_session()


if __name__ == '__main__':
    flags.mark_flag_as_required('bt_test')
    flags.mark_flag_as_required('bt_dut')
    app.run(main)
