"""
.. module:: activate
    :platform: Darwin, Linux, Unix, Windows
    :synopsis: Module that is utilized by test files to ensure the test environment is initialized in
               the correct order.

.. moduleauthor:: Myron Walker <myron.walker@gmail.com>
"""

__author__ = "Myron Walker"
__copyright__ = "Copyright 2020, Myron W Walker"
__credits__ = []
__version__ = "1.0.0"
__maintainer__ = "Myron Walker"
__email__ = "myron.walker@gmail.com"
__status__ = "Development" # Prototype, Development or Production
__license__ = "MIT"

import os
import sys

from datetime import datetime
from akit.exceptions import AKitConfigurationError, AKitSemanticError, TRACEBACK_CONFIG, VALID_MEMBER_TRACE_POLICY

# Perform a sematic check to see who is importing the akit.activation.base module.  We
# need to make sure that the user is following the proper semantics and importing an activation
# profile and not directly importing this module.  This will enforce the setting of the
# activation profile in the global variables and enforce a proper environment activation
# sequence is followed.
importer_frame = sys._getframe()
while True:
    importer_frame = importer_frame.f_back
    if importer_frame.f_code.co_filename.find("importlib") < 0:
        break
if "__activation_profile__" not in importer_frame.f_locals:
    errmsg = "The 'akit.activation.base' should not be directly imported." \
             "  The environment activation should always happen by importing" \
             " an activation profile module."
    raise AKitSemanticError(errmsg)

# =======================================================================================
# The way we start up the test framework and the order which things come up in is a very
# important part of the automation process.  It effects whether or not logging is brought
# up consistently before all modules start using it.  It ensures that no matter how we
# enter into an automation process, whether via a test runner, terminal, or debugging a single
# file that we properly parse arguments and settings and launch the automation process
# consistently.
#
# Because of these necessities, we setup the activate module so it is the first thing
# scripts and tests files that consume the test framework will import to ensure the
# startup process is always consistent
#
# The framework has a special activation module :module:`akit.environment.console` that is
# used when bringing up the test framework in a console.  This special method redirects

# Activation Step - 1: Force the CONFIGURATION_MAP global variable into existance and
# pull in the OVERRIDE_CONFIGURATION dictionary.
from akit.environment.configuration import OVERRIDE_CONFIGURATION, CONFIGURATION_MAP

# Activation Step - 2: Force the global shared context to load, we want this to happen as early
# as possible because we don't want to every replace its reference or invalidate
# any references to it that someone might have acquired.
from akit.environment.context import Context # pylint: disable=wrong-import-position

ctx = Context()

# Activation Step - 3: Process the environment variable overrides for any of the AKIT configuration
# variables. This needs to happen before we load or create an initial user configuration
# because the variables may effect the values we write into the user configuration file.
from akit.environment.variables import (
    AKIT_VARIABLES,
    JOB_TYPES,
    
    LOG_LEVEL_NAMES,
    normalize_variable_whitespace
)

# Activation Step - 4: Load the user and runtime configuration and add it to the CONFIGURATION_MAP
# 'ChainMap' so the runtime settings can take precedence over the user default settings. 
from akit.environment.configuration import load_runtime_configuration, load_user_configuration

AKIT_DIR = AKIT_VARIABLES.AKIT_DIR

DEFAULT_PATH_EXPANSIONS = [
    os.path.expanduser,
    os.path.expandvars,
    os.path.abspath
]
def expand_path(path_in, expansions=DEFAULT_PATH_EXPANSIONS):

    path_out = path_in
    for expansion_func in expansions:
        path_out = expansion_func(path_out)

    return path_out


# The runtime configuration should be first so it
# has the highest priority.
runtime_config = load_runtime_configuration()
CONFIGURATION_MAP.maps.append(runtime_config)

# User config is last in the map list because runtime
# overrides user configuration, we do it this way because
# the runtime configuration might be generated by the
# CI system.  The user configuration is persisted and
# can have settings that stay with the machine.
user_config = load_user_configuration()
CONFIGURATION_MAP.maps.append(user_config)


# Activation Step - 5: Write the information into the context that is not persisted and is
# instance specific.  These variables are stored in the environment section
# of the configuration and overall global context.
env = ctx.lookup("/environment")

env["apod"] = AKIT_VARIABLES.AKIT_APOD_NAME

env["build"] = {
    "branch": AKIT_VARIABLES.AKIT_BUILD_BRANCH,
    "name": AKIT_VARIABLES.AKIT_BUILD_NAME,
    "flavor": AKIT_VARIABLES.AKIT_BUILD_FLAVOR,
    "url": AKIT_VARIABLES.AKIT_BUILD_URL,
}

env["job"] = {
    "initiator": AKIT_VARIABLES.AKIT_JOB_INITIATOR,
    "label": AKIT_VARIABLES.AKIT_JOB_LABEL,
    "name": AKIT_VARIABLES.AKIT_JOB_NAME,
    "owner": AKIT_VARIABLES.AKIT_JOB_OWNER,
    "type": AKIT_VARIABLES.AKIT_JOBTYPE
}

# Set the jobtype on the base environment for convenience
env["jobtype"] = AKIT_VARIABLES.AKIT_JOBTYPE

env["breakpoints"] = AKIT_VARIABLES.AKIT_BREAKPOINTS
env["debugger"] = AKIT_VARIABLES.AKIT_DEBUGGER
env["testroot"] = AKIT_VARIABLES.AKIT_TESTROOT
env["starttime"] = AKIT_VARIABLES.AKIT_STARTTIME
env["runid"] = AKIT_VARIABLES.AKIT_RUNID
env["pid"] = os.getpid()



# We set all the variables for config file options from the environment
# we just loaded, these might get overridden late but that is ok

configuration = ctx.lookup("/configuration", default={})
configuration["skip-devices"] = []

if "diagnostics" not in configuration:
    configuration["diagnostics"] = {}

if AKIT_VARIABLES.AKIT_TRACEBACK_POLICY_OVERRIDE is not None:
    if AKIT_VARIABLES.AKIT_TRACEBACK_POLICY_OVERRIDE not in VALID_MEMBER_TRACE_POLICY:
        configuration["diagnostics"]["traceback-policy-override"] = AKIT_VARIABLES.AKIT_TRACEBACK_POLICY_OVERRIDE
    else:
        errmsg = "Invalid traceback policy environment value. AKIT_TRACEBACK_POLICY_OVERRIDE={}".format(
            AKIT_VARIABLES.AKIT_TRACEBACK_POLICY_OVERRIDE
        )
        raise AKitConfigurationError(errmsg)

# If a traceback policy override was set, apply it in the TRACEBACK_CONFIG in the akit.exceptions module
if "traceback-policy-override" in configuration["diagnostics"]:
    TRACEBACK_CONFIG.TRACEBACK_POLICY_OVERRIDE = configuration["diagnostics"]["traceback-policy-override"]

config_files = configuration.lookup("/paths", default={})
config_files["credentials"] = AKIT_VARIABLES.AKIT_CONFIG_CREDENTIALS
config_files["landscape"] = AKIT_VARIABLES.AKIT_CONFIG_LANDSCAPE
config_files["runtime"] = AKIT_VARIABLES.AKIT_CONFIG_RUNTIME
config_files["topology"] = AKIT_VARIABLES.AKIT_CONFIG_TOPOLOGY
config_files["user"] = AKIT_VARIABLES.AKIT_CONFIG_USER

# Activation Step - 5: After 
if AKIT_VARIABLES.AKIT_LOG_LEVEL_CONSOLE is not None and AKIT_VARIABLES.AKIT_LOG_LEVEL_CONSOLE in LOG_LEVEL_NAMES:
    console_level = AKIT_VARIABLES.AKIT_LOG_LEVEL_CONSOLE
else:
    console_level = "INFO"
    AKIT_VARIABLES.AKIT_LOG_LEVEL_CONSOLE = console_level

if AKIT_VARIABLES.AKIT_LOG_LEVEL_FILE is not None and AKIT_VARIABLES.AKIT_LOG_LEVEL_FILE in LOG_LEVEL_NAMES:
    logfile_level = AKIT_VARIABLES.AKIT_LOG_LEVEL_FILE
else:
    logfile_level = "DEBUG"
    AKIT_VARIABLES.AKIT_LOG_LEVEL_FILE = logfile_level

if AKIT_VARIABLES.AKIT_SKIP_DEVICES is not None:
    devices_list = normalize_variable_whitespace(AKIT_VARIABLES.AKIT_SKIP_DEVICES).split(" ")
    configuration["skip-devices-override"] = devices_list

fill_dict = {
    "starttime": str(AKIT_VARIABLES.AKIT_STARTTIME).replace(" ", "T")
}

jobtype = ctx.lookup("/environment/job/type", default=JOB_TYPES.TESTRUN)

# We want to pull the console and testresults value from the configuration, because if its not there it
# will be set from the default_dir_template variable
default_dir_template = os.path.join(AKIT_VARIABLES.AKIT_HOME_DIRECTORY, "results", "console", "%(starttime)s")
outdir_template_consoleresults = configuration.lookup("/path-templates/consoleresults", default=default_dir_template)
dir_consoleresults = default_dir_template % fill_dict
configuration.insert("/paths/consoleresults", dir_consoleresults)

default_dir_template = os.path.join(AKIT_VARIABLES.AKIT_HOME_DIRECTORY, "results", "testresults", "%(starttime)s")
outdir_template_testresults = configuration.lookup("/path-templates/testresults", default=default_dir_template)
dir_testresults = default_dir_template % fill_dict
configuration.insert("/paths/testresults", dir_testresults)

outdir_full = None
# Figure out which output directory to set as the current process output directory.  The output directory
# determines where logging will go and is different depending on the activation mode of the test framework
if AKIT_VARIABLES.AKIT_OUTPUT_DIRECTORY is not None:
    outdir_full = expand_path(AKIT_VARIABLES.AKIT_OUTPUT_DIRECTORY % fill_dict)
    env["output_directory"] = outdir_full

    if jobtype == JOB_TYPES.TESTRUN:
        configuration.insert("/paths/testresults", outdir_full)
    elif jobtype == JOB_TYPES.CONSOLE:
        configuration.insert("/paths/testresults", outdir_full)
else:
    if jobtype == JOB_TYPES.CONSOLE:
        outdir_full = expand_path(outdir_template_consoleresults % fill_dict)
        env["output_directory"] = outdir_full
    else:
        outdir_full = expand_path(outdir_template_testresults % fill_dict)
        env["output_directory"] = outdir_full

results_configuration = {}
results_configuration["static-resource-dest-dir"] = expand_path(AKIT_VARIABLES.AKIT_RESULTS_STATIC_RESOURCE_DEST_DIR)
results_configuration["static-resource-src-dir"] = expand_path(AKIT_VARIABLES.AKIT_RESULTS_STATIC_RESOURCE_SRC_DIR)

if AKIT_VARIABLES.AKIT_RESULTS_HTML_TEMPLATE is not None:
    results_configuration["html-template"] = AKIT_VARIABLES.AKIT_RESULTS_HTML_TEMPLATE
else:
    results_configuration["html-template"] = expand_path(os.path.join(AKIT_DIR, "templates", "testsummary.html"))

configuration["results-configuration"] = results_configuration

if jobtype != "console":
    env["behaviors"] = {
        "log-landscape-declaration": True,
        "log-landscape-scan": True
    }

# Activation Step - 7: Import the logging module so we can be the trigger the logging configuration
# for standard out
import akit.xlogging.foundations # pylint: disable=unused-import,wrong-import-position
