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

""" This version of sitecustomize will
1. initialize the SkyWalking Python Agent.
2. invoke an existing sitecustomize.py.
Requires Python 3.7+ (os.register_at_fork) to work properly for all cases.
A higher Python version is always recommended.

When executing commands with `sw-python run command`
This particular sitecustomize module will be picked up by any valid replacement
process(command) invoked by os.execvp in runner.py, thus will be seen by potentially
incompatible Python versions and system interpreters, which certainly will cause problems.
Therefore, an attempt to use command leading to a different Python interpreter should be stopped.
"""
import importlib
import logging
import os
import platform
import sys


def _get_sw_loader_logger():
    """ Setup a new logger with passed skywalking CLI env vars,
    don't import from skywalking, it may not be on sys.path
    if user misuses the CLI to run programs out of scope
    """
    from logging import getLogger
    logger = getLogger('skywalking-loader')
    logger.setLevel('INFO')
    ch = logging.StreamHandler()
    formatter = logging.Formatter('%(name)s [pid:%(process)d] [%(threadName)s] [%(levelname)s] %(message)s')
    ch.setFormatter(formatter)
    logger.addHandler(ch)
    logger.propagate = False
    if os.environ.get('SW_AGENT_SW_PYTHON_CLI_DEBUG_ENABLED') == 'True':  # set from the original CLI runner
        logger.setLevel(level=logging.DEBUG)
    return logger


_sw_loader_logger = _get_sw_loader_logger()
_sw_loader_logger_debug_enabled = _sw_loader_logger.isEnabledFor(logging.DEBUG)
# DEBUG messages in case execution goes wrong
if _sw_loader_logger_debug_enabled:
    _sw_loader_logger.debug('---------------sitecustomize.py---------------')
    _sw_loader_logger.debug(f'Successfully imported sitecustomize.py from `{__file__}`')
    _sw_loader_logger.debug(f'You are inside working dir - {os.getcwd()}')
    _sw_loader_logger.debug(f'Using Python version - {sys.version} ')
    _sw_loader_logger.debug(f'Using executable at - {sys.executable}')
    _sw_loader_logger.debug(f'System Base Python executable location {sys.base_prefix}')

if sys.prefix != sys.base_prefix:
    _sw_loader_logger.debug('[The SkyWalking agent bootstrapper is running inside a virtual environment]')

# It is possible that someone else also has a sitecustomize.py
# in sys.path either set by .pth files or manually, we need to run them as well
# The path to user sitecustomize.py is after ours in sys.path, needs to be imported here.
# just to be safe, remove loader thoroughly from sys.path and also sys.modules

cleared_path = [p for p in sys.path if p != os.path.dirname(__file__)]
sys.path = cleared_path  # remove this version from path
loaded = sys.modules.pop('sitecustomize', None)  # pop sitecustomize from loaded

# now try to find the original sitecustomize provided in user env
try:
    loaded = importlib.import_module('sitecustomize')
    _sw_loader_logger.debug(f'Found user sitecustomize file {loaded}, imported')
except ImportError:  # ModuleNotFoundError
    _sw_loader_logger.debug('Original sitecustomize module not found, skipping.')
finally:  # surprise the import error by adding loaded back
    sys.modules['sitecustomize'] = loaded

# This sitecustomize by default doesn't remove the loader dir from PYTHONPATH,
# Thus, subprocesses and multiprocessing also inherits this sitecustomize.py
# This behavior can be turned off using a user provided env below
# os.environ['SW_PYTHON_BOOTSTRAP_PROPAGATE']

if os.environ.get('SW_AGENT_SW_PYTHON_BOOTSTRAP_PROPAGATE') == 'False':
    if os.environ.get('PYTHONPATH'):
        partitioned = os.environ['PYTHONPATH'].split(os.path.pathsep)
        loader_path = os.path.dirname(__file__)
        if loader_path in partitioned:  # check if we are already removed by a third-party
            partitioned.remove(loader_path)
            os.environ['PYTHONPATH'] = os.path.pathsep.join(partitioned)
            _sw_loader_logger.debug('Removed loader from PYTHONPATH, spawned process will not have agent enabled')

# Note that users could be misusing the CLI to call a Python program that
# their Python env doesn't have SkyWalking installed. Or even call another
# env that has another version of SkyWalking installed, which is leads to problems.
# Even if `import skywalking` was successful, doesn't mean its the same one where sw-python CLI was run.
# Needs further checking for Python versions and prefix that passed down from CLI in os.environ.

cli_python_version = os.environ.get('SW_PYTHON_VERSION')
cli_python_prefix = os.environ.get('SW_PYTHON_PREFIX')

version_match = cli_python_version == platform.python_version()

# windows can sometimes make capitalized path, lower them can help
prefix_match = cli_python_prefix.lower() == os.path.realpath(os.path.normpath(sys.prefix)).lower()

if not (version_match and prefix_match):

    _sw_loader_logger.error(
        f'\nPython used by sw-python CLI - v{cli_python_version} at {cli_python_prefix}\n'
        f'Python used by your actual program - v{platform.python_version()} '
        f'at {os.path.realpath(os.path.normpath(sys.prefix))}'
    )
    _sw_loader_logger.error('The sw-python CLI was instructed to run a program '
                            'using an different Python installation '
                            'this is not safe and loader will not proceed. '
                            'Please make sure that sw-python CLI, skywalking agent and your '
                            'application are using the same Python executable. '
                            'Rerun with debug flag, `sw-python -d run <your_python_command>` for '
                            'some troubleshooting information.'
                            'use `which sw-python` to find out the invoked CLI location')
    os._exit(1)  # noqa: do not go further

else:
    from skywalking import config
    from skywalking.agent import agent

    _sw_loader_logger.info(f'Process-{os.getpid()}, running sitecustomize.py from {__file__}')
    # also override debug for skywalking agent itself
    if os.environ.get('SW_AGENT_SW_PYTHON_CLI_DEBUG_ENABLED') == 'True':  # set from the original CLI runner
        config.agent_logging_level = 'DEBUG'

    # Currently supports configs read from os.environ

    # This is for python 3.6 - 3.7(maybe) argv is not set for embedded interpreter thus will cause failure in
    # those libs that imports argv from sys, we need to set it manually if it's not there already
    # otherwise the plugin install will fail and things won't work properly, example - Sanic
    if not hasattr(sys, 'argv'):
        sys.argv = ['']

    # noinspection PyBroadException
    try:
        _sw_loader_logger.debug('SkyWalking Python Agent starting, loader finished.')
        prefork_server_detected = os.getenv('prefork')
        if prefork_server_detected:
            config.agent_instance_name = f'{config.agent_instance_name}-master({os.getpid()})'
            _sw_loader_logger.info(f'Prefork server is detected ({prefork_server_detected}), '
                                   f'agent will be automatically started after fork. \n'
                                   f'Please monitor with care as this is an experimental feature, '
                                   f'it may not work well with all {prefork_server_detected} configurations.')
            if prefork_server_detected == 'uwsgi':
                # We don't do anything here, as uwsgi cannot trigger after_in_child hook
                # We use the already injected uwsgi postfork hook to start the agent (bootstrap/hooks/uwsgi_hook.py)
                ...
            if prefork_server_detected == 'gunicorn':
                # We need to enable experimental fork support for Gunicorn
                config.agent_experimental_fork_support = True
                # Luckily Gunicorn is based on os.fork and can be safely forked with experimental fork support
                agent.start()
        else:  # Either there's some prefix like supervisor, or it simply doesn't use uwsgi/gunicorn
            agent.start()  # CHECK: Not sure what happens when supervisor + gunicorn is used? Will it even work?

    except Exception:
        _sw_loader_logger.exception('SkyWalking Python Agent failed to start, please inspect your package installation.'
                                    'Report issue if you think this is a bug, along with the log produced by '
                                    'specifying the -d debug flag.')
