'''
Verify log file naming behavior.
'''
#  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
import ports

Test.Summary = '''
Verify log file naming behavior.
'''


class LogFilenamesTest:
    """ Common test configuration logic across the filename tests.
    """

    # A counter for the ATS process to make each of them unique.
    __ts_counter = 1

    # The default log names for the various system logs.
    default_log_data = {'diags': 'diags.log', 'error': 'error.log'}

    def __init__(self, description, log_data=default_log_data):
        ''' Handle initialization tasks common across the tests.

        Args:
            description (str): The description of the test. This is passed to
            the TestRun.

            log_data (dict): The log name information passed to the
            MakeATSProcess extension.
        '''
        self.__description = description
        self.ts = self.__configure_traffic_server(log_data)
        self.tr = self.__configure_traffic_TestRun(description)
        self.__configure_await_TestRun(self.sentinel_log_path)

    def __configure_traffic_server(self, log_data):
        ''' Common ATS configuration logic.

        Args:
            log_data (dict): The log name information passed to the
            MakeATSProcess extension.

        Return:
            The traffic_server process.
        '''
        self._ts_name = f"ts{LogFilenamesTest.__ts_counter}"
        LogFilenamesTest.__ts_counter += 1
        self.ts = Test.MakeATSProcess(self._ts_name, use_traffic_out=False, log_data=log_data)
        self.ts.Disk.records_config.update(
            {
                'proxy.config.diags.debug.enabled': 0,
                'proxy.config.diags.debug.tags': 'log',
                'proxy.config.log.periodic_tasks_interval': 1,
            })

        # Intentionally retrieve a port that is closed, that is no server is
        # listening on it. We will use this to attempt talking with a
        # non-existent server, which will result in an error log entry.
        ports.get_port(self.ts, 'closed_port')
        self.ts.Disk.remap_config.AddLines(
            [
                f'map /server/down http://127.0.0.1:{self.ts.Variables.closed_port}',
                'map / https://trafficserver.apache.org @action=deny',
            ])

        # The following log is configured so that we can wait upon it being
        # written so we know that ATS is done writing logs.
        self.sentinel_log_filename = "sentinel"
        self.ts.Disk.logging_yaml.AddLine(
            f'''
            logging:
              formats:
                - name: url_and_return_code
                  format: "%<pqu>: %<pssc>"
              logs:
                - filename: {self.sentinel_log_filename}
                  format: url_and_return_code
            ''')

        self.sentinel_log_path = os.path.join(self.ts.Variables.LOGDIR, f"{self.sentinel_log_filename}.log")

        return self.ts

    def __configure_await_TestRun(self, log_path):
        ''' Configure a TestRun that awaits upon the provided log_path to
        exist.

        Args:
            log_path (str): The log file upon which we will wait.
        '''
        description = self.__description
        tr = Test.AddTestRun(f'Awaiting log files to be written for: {description}')
        condwait_path = os.path.join(Test.Variables.AtsTestToolsDir, 'condwait')
        tr.Processes.Default.Command = f'{condwait_path} 60 1 -f {log_path}'
        tr.Processes.Default.ReturnCode = 0

    def __configure_traffic_TestRun(self, description):
        ''' Configure a TestRun to run the expected transactions.

        Args:
            description (str): The description to use for the TestRun.
        '''
        tr = Test.AddTestRun(f'Run traffic for: {description}')
        tr.MakeCurlCommand(
            f'http://127.0.0.1:{self.ts.Variables.port}/some/path --verbose --next '
            f'http://127.0.0.1:{self.ts.Variables.port}/server/down --verbose',
            ts=self.ts)
        tr.Processes.Default.ReturnCode = 0
        tr.Processes.Default.StartBefore(self.ts)

    def configure_named_custom_log(self, custom_log_filename):
        """ Configure ATS to log to the custom log file via logging.yaml.

        Args:
            custom_log_filename (str): The name of the custom log file to
            configure.

        Return:
            The path to the configured custom log file.
        """
        self.custom_log_filename = custom_log_filename
        self.ts.Disk.logging_yaml.AddLine(
            f'''
                - filename: {custom_log_filename}
                  format: url_and_return_code
            ''')

        if custom_log_filename in ('stdout', 'stderr'):
            self.custom_log_path = custom_log_filename
            if custom_log_filename == 'stdout':
                self.ts.Disk.custom_log = self.ts.Streams.stdout
            else:
                self.ts.Disk.custom_log = self.ts.Streams.stderr
        else:
            self.custom_log_path = os.path.join(self.ts.Variables.LOGDIR, f"{custom_log_filename}.log")
            self.ts.Disk.File(self.custom_log_path, id="custom_log")
        return self.custom_log_path

    def set_log_expectations(self):
        ''' Configure sanity checks for each of the log types (diags, error,
        etc.) to verify they are emitting the expected content.
        '''

        diags_path = self.ts.Disk.diags_log.AbsPath
        self.ts.Disk.diags_log.Content += Testers.ContainsExpression(
            "Traffic Server is fully initialized", f"{diags_path} should contain traffic_server diag messages")

        error_log_path = self.ts.Disk.error_log.AbsPath
        self.ts.Disk.error_log.Content += Testers.ContainsExpression(
            "CONNECT: attempt fail", f"{error_log_path} should contain connection error messages")

        custom_log_path = self.ts.Disk.custom_log.AbsPath
        self.ts.Disk.custom_log.Content += Testers.ContainsExpression(
            "https://trafficserver.apache.org/some/path: 403", f"{custom_log_path} should contain the custom transaction logs")


class DefaultNamedTest(LogFilenamesTest):
    ''' Verify that if custom names are not configured, then the default
    'diags.log' and 'error.log' are written to.
    '''

    def __init__(self):
        super().__init__('default log filename configuration')

        self.configure_named_custom_log('my_custom_log')
        self.set_log_expectations()


class CustomNamedTest(LogFilenamesTest):
    ''' Verify that the user can assign custom filenames to diags.log, etc.
    '''

    def __init__(self):
        log_data = {'diags': 'my_diags.log', 'error': 'my_error.log'}
        super().__init__('specify log filename configuration', log_data)

        # Configure custom names for diags.log, etc.
        self.ts.Disk.records_config.update(
            {
                'proxy.config.diags.logfile.filename': 'my_diags.log',
                'proxy.config.error.logfile.filename': 'my_error.log',
            })

        self.configure_named_custom_log('my_custom_log')
        self.set_log_expectations()


class stdoutTest(LogFilenamesTest):
    ''' Verify that we can configure the logs to go to stdout.
    '''

    def __init__(self):

        log_data = {'diags': 'stdout', 'error': 'stdout'}
        super().__init__('specify logs to go to stdout', log_data)

        # Configure custom names for diags.log, etc.
        self.ts.Disk.records_config.update(
            {
                'proxy.config.diags.logfile.filename': 'stdout',
                'proxy.config.error.logfile.filename': 'stdout',
            })

        self.configure_named_custom_log('stdout')

        # The diags.log file will not be created since we are piping to stdout.
        # Therefore, simply wait upon the port being open.
        self.ts.Ready = When.PortOpen(self.ts.Variables.port)
        self.set_log_expectations()


class stderrTest(LogFilenamesTest):
    '''
    Verify that we can configure the logs to go to stderr.
    '''

    def __init__(self):

        log_data = {'diags': 'stderr', 'error': 'stderr'}
        super().__init__('specify logs to go to stderr', log_data)

        # Configure custom names for diags.log, etc.
        self.ts.Disk.records_config.update(
            {
                'proxy.config.diags.logfile.filename': 'stderr',
                'proxy.config.error.logfile.filename': 'stderr',
            })

        self.configure_named_custom_log('stderr')

        # The diags.log file will not be created since we are piping to stderr.
        # Therefore, simply wait upon the port being open.
        self.ts.Ready = When.PortOpen(self.ts.Variables.port)
        self.set_log_expectations()


#
# Run the tests.
#
DefaultNamedTest()
CustomNamedTest()
stdoutTest()

# The following stderr test can be run successfully by hand using the replay
# files from the sandbox. All the expected output goes to stderr. However, for
# some reason during the AuTest run, the stderr output stops emitting after the
# logging.yaml file is parsed. This is left here for now because it is valuable
# for use during development, but it is left commented out so that it doesn't
# produce the false failure in CI and developer test runs.
# stderrTest()
