'''
Implement the Proxy Verifier test server extension.
'''
#  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 ports import get_port
from verifier_common import create_address_argument, substitute_context_in_replay_file


def _configure_server(
        obj,
        process,
        name,
        replay_path,
        http_ports=None,
        https_ports=None,
        http3_ports=None,
        ssl_cert='',
        ca_cert='',
        verbose=True,
        other_args='',
        context=None):
    """
    Configure the provided process to run a verifier-server command.

    Args
        obj: An object that has a RunDirectory attribute.

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

        For all other arguments, see MakeVerifierServerProcess.
    """
    # Create the directory we will setup for the replay binaries to run under.
    server_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-server that gets run.
    bin_dir = 'bin'
    process.Env['SERVER_BIN_PATH'] = bin_dir
    bin_path = os.path.join(server_dir, bin_dir)
    process.Env['PATH'] = bin_path + os.pathsep + process.ComposeEnv()['PATH']
    process.Setup.Copy(process.Variables.VerifierBinPath, bin_path, CopyLogic.SoftFiles)

    # Configure the verifier-server command line arguments.
    command = "verifier-server run "

    if http_ports is None:
        get_port(process, "http_port")
        http_ports = [process.Variables.http_port]
    else:
        process.Variables['http_port'] = http_ports[0]

    if len(http_ports) > 0:
        command += "--listen-http "
        command += create_address_argument(http_ports)
        command += " "

    if https_ports is None:
        get_port(process, "https_port")
        https_ports = [process.Variables.https_port]
    else:
        process.Variables['https_port'] = https_ports[0]

    if len(https_ports) > 0:
        command += '--listen-https '
        command += create_address_argument(https_ports)
        command += " "

    if http3_ports is None:
        get_port(process, "http3_port")
        http3_ports = [process.Variables.http3_port]
    else:
        process.Variables['http3_port'] = http3_ports[0]

    # Add in the following when Proxy Verifier server-side HTTP/3 is supported.
    # if len(http3_ports) > 0:
    #     command += '--listen-http3 '
    #     command += create_address_argument(http3_ports)
    #     command += " "
    #
    #     qlog_basename = "qlog_directory"
    #     qlog_dir = os.path.join(server_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", "server.pem")

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

            tls_secrets_log_filename = "tls_secrets.txt"
            tls_secrets_log_path = os.path.join(server_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(server_dir, os.path.basename(ca_cert))
            process.Setup.Copy(ca_cert, run_ca_cert, CopyLogic.SoftFiles)
            command += ' --ca-certs "{}" '.format(run_ca_cert)

    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)

        run_replay_path = os.path.join(server_dir, os.path.basename(replay_path))
        # Create a copy of the replay directory in the run directory.
        process.Setup.Copy(replay_path, run_replay_path, CopyLogic.SoftFiles)
        command += "{} ".format(run_replay_path)

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

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

    process.Ready = When.PortOpenv4(process.Variables.http_port)
    process.ReturnCode = 0

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


def MakeVerifierServerProcess(
        test,
        name,
        replay_path,
        http_ports=None,
        https_ports=None,
        http3_ports=None,
        ssl_cert='',
        ca_cert='',
        verbose=True,
        other_args='',
        context=None):
    """
    Create a verifier-server process for the Test.

    Args:
        test: (Test) The Test object being updated with the server process.

        name: (str) The name to apply to this particular verifier-server instance.
            Individual verifier-server 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 listen on. If not
            passed in, a single open port will be selected.

        https_ports: (list of ints) The set of HTTPS ports to listen on. If not
            passed in, a single open port will be selected.

        http3_ports: (list of ints) The set of HTTP3 ports to listen on. If not
            passed in, a single open port will be selected.

        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-server.

        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.
    Raises:
        ValueError if https_ports is non-empty and a valid ssl_cert could not
            be derived.
            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.
    """
    server = test.Processes.Process(name)
    _configure_server(
        test, server, name, replay_path, http_ports, https_ports, http3_ports, ssl_cert, ca_cert, verbose, other_args, context)
    return server


def AddVerifierServerProcess(
        run,
        name,
        replay_path,
        http_ports=None,
        https_ports=None,
        http3_ports=None,
        ssl_cert='',
        ca_cert='',
        verbose=True,
        other_args='',
        context=None):
    """
    Create a verifier-server process and configure it for the given TestRun.

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

        For all other arguments, see MakeVerifierServerProcess.

    Returns:
        The newly created server process.
    """

    server = run.Processes.Process(name)
    _configure_server(
        run, server, name, replay_path, http_ports, https_ports, http3_ports, ssl_cert, ca_cert, verbose, other_args, context)

    client = run.Processes.Default
    client.StartBefore(server)

    return server


##########################################################################
ExtendTest(MakeVerifierServerProcess, name="MakeVerifierServerProcess")
ExtendTestRun(AddVerifierServerProcess, name="AddVerifierServerProcess")
