'''
Implement the Proxy Verifier client extensions.
'''
#  Licensed to the Apache Software Foundation (ASF) under one
#  or more contributor license agreements.  See the NOTICE file
#  distributed with this work for additional information
#  regarding copyright ownership.  The ASF licenses this file
#  to you 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.


import os
from verifier_common import create_address_argument, substitute_context_in_replay_file


def _configure_client(obj, process, name, replay_path, http_ports=None,
                      https_ports=None, http3_ports=None, keys=None,
                      ssl_cert='', ca_cert='', verbose=True, other_args='',
                      context=None):
    """
    Configure the process for running the verifier-client.

    Args:
        obj: An object that has a RunDirectory attribute.

        process: (Process) The test process to configure for verifier-client.

        See AddVerifierClientProcess for a description of all other arguments.
    """

    # Create the directory we will setup for the replay binaries to run under.
    client_dir = os.path.join(obj.RunDirectory, name)

    # Create a link of the binary to the rundir and set up the PATH variable.
    # This will be the verifier-client that gets run.
    bin_dir = 'bin'
    process.Env['CLIENT_BIN_PATH'] = bin_dir
    bin_path = os.path.join(client_dir, bin_dir)
    process.Env['PATH'] = bin_path + os.pathsep + process.ComposeEnv()['PATH']
    process.Setup.Copy(process.Variables.VerifierBinPath, bin_path, CopyLogic.SoftFiles)

    if http_ports is None and https_ports is None:
        raise ValueError("http_ports and/or https_ports must be supplied.")

    # Configure the verifier-client command line arguments.
    command = "verifier-client run "
    if replay_path:
        if context:
            # replace the passed replay file with the new one generated using the passed
            # context
            replay_path = substitute_context_in_replay_file(process, replay_path, context)
        # Create a copy of the replay directory in the run directory.
        run_replay_path = os.path.join(client_dir, os.path.basename(replay_path))
        process.Setup.Copy(replay_path, run_replay_path, CopyLogic.SoftFiles)
        command += " {} ".format(run_replay_path)

    if not http_ports:
        http_ports = [8080]
    if http_ports:
        command += "--connect-http "
        command += create_address_argument(http_ports)
        command += " "

    if not https_ports:
        https_ports = [4443]
    if https_ports:
        command += "--connect-https "
        command += create_address_argument(https_ports)
        command += " "

    if not http3_ports:
        http3_ports = [4443]
    if http3_ports:
        command += "--connect-http3 "
        command += create_address_argument(http3_ports)
        command += " "

        qlog_basename = "qlog_directory"
        qlog_dir = os.path.join(client_dir, qlog_basename)
        command += f"--qlog-dir {qlog_dir} "
        obj.Variables['qlog_dir'] = qlog_dir

    if https_ports or http3_ports:
        if ssl_cert == '':
            ssl_cert = os.path.join(obj.Variables["AtsTestToolsDir"],
                                    "proxy-verifier", "ssl", "client.pem")

            if not os.path.isfile(ssl_cert):
                raise ValueError("Tried to use '{}' for --client-cert, but it is not "
                                 "a valid file.".format(ssl_cert))
        if ssl_cert:
            run_ssl_cert = os.path.join(client_dir, os.path.basename(ssl_cert))
            process.Setup.Copy(ssl_cert, run_ssl_cert, CopyLogic.SoftFiles)
            command += ' --client-cert "{}" '.format(run_ssl_cert)

            tls_secrets_log_filename = "tls_secrets.txt"
            tls_secrets_log_path = os.path.join(client_dir, tls_secrets_log_filename)
            command += f"--tls-secrets-log-file {tls_secrets_log_path} "
            obj.Variables['tls_secrets_log_path'] = tls_secrets_log_path

        if ca_cert == '':
            ca_cert = os.path.join(obj.Variables["AtsTestToolsDir"],
                                   "proxy-verifier", "ssl", "ca.pem")

            if not os.path.isfile(ca_cert):
                raise ValueError("Tried to use '{}' for --ca-certs, but it is not "
                                 "a valid file.".format(ca_cert))
        if ca_cert:
            run_ca_cert = os.path.join(client_dir, os.path.basename(ca_cert))
            process.Setup.Copy(ca_cert, run_ca_cert, CopyLogic.SoftFiles)
            command += ' --ca-certs "{}" '.format(run_ca_cert)

    if verbose:
        command += ' --verbose diag '

    if other_args:
        command += " {}".format(other_args)

    if keys is not None:
        command += " --keys {}".format(keys)

    process.Command = command
    process.ReturnCode = 0

    process.Streams.stdout = Testers.ExcludesExpression(
        "Violation|Invalid status",
        "There should be no Proxy Verifier violation errors.")


def AddVerifierClientProcess(run, name, replay_path, http_ports=None,
                             https_ports=None, http3_ports=None, keys=None,
                             ssl_cert='', ca_cert='', verbose=True, other_args='',
                             context=None):
    """
    Set the Default process of the test run to a verifier-client Process.

    Args:
        run: (TestRun) The test run to which the client process is added.

        name: (str) The name to apply to this particular verifier-client instance.
            Individual verifier-client processes must have unique names to
            distinguish between them.

        replay_path: (path) The file or directory containing the traffic replay
            specification.

        http_ports: (list of ints) The set of HTTP ports to connect on.

        https_ports: (list of ints) The set of HTTPS ports to connect on.

        http3_ports: (list of ints) The set of HTTP3 ports to connect on.

        ssl_cert: (path) The location of the cert for HTTPS encryption. If this
            is not provided and stock ssl_cert will be used.

        ca_cert: (path) The location of the CA for HTTPS encryption. If this
            is not provided a stock CA will be used.

        verbose: (bool) Whether to enable debug level logging.

        other_args: (str) Any other arbitrary options to pass to verifier-client.

        context: Any dictionary-like object with keys that match the placeholders
                 in the replay file.
                 Template strings support $-based substitutions in the replay file.
                 You can refer to https://docs.python.org/3/library/string.html#template-strings
                 for more information how to add template strings to the replay file.
    Returns:
        The newly constructed verifier-client for the test run, which is also the
        Default Process of the test run.
    Throws:
        ValueError:
            If context substitution is expected to be done but a directory is passed as a
            replay_path.
        OSError in case of any issues related to I/O error, ie: File Not found for the replay
            file when a context substitution is expected.
        KeyError if placeholders are missing from the mapping between context and the replay file.
    """

    p = run.Processes.Default
    _configure_client(run, p, name, replay_path, http_ports, https_ports,
                      http3_ports, keys, ssl_cert, ca_cert, verbose,
                      other_args, context)
    return p


##########################################################################
ExtendTestRun(AddVerifierClientProcess, name="AddVerifierClientProcess")
