#!/usr/bin/env python3

"""CLI functions for running a job or generate a CI config."""

from os import makedirs
import pkg_resources
from jinja2 import Environment, FileSystemLoader
from chainedci.config import getConfig
from chainedci.inventory import Inventory
from chainedci.init_project import init_project
from chainedci.log import log
from chainedci.options import Options
from chainedci.scenario import Scenario
from chainedci.tools import raise_ex
from chainedci.version import __version__


def main():
    """Read sys opts and run cli commands."""
    opts = Options()
    inv = Inventory(opts.opts.inventory)
    if opts.opts.mode == 'run':
        log.info("Start running the scenario job")
        run(opts, inv)
    elif opts.opts.mode == 'generate':
        log.info("Start generating the ci file")
        generate(inv, opts.opts.scenario_name)
    elif opts.opts.mode == 'init':
        log.info("Generate the chained-ci structure")
        init_project()
    else:
        raise_ex(ValueError, f'Unknown command "{opts.opts.mode}"')


def _generate_scenario(env, defaults, scenario_name,
                       scenario_config):
    """
    Generate scenarios into gitlab-ci.yml files.

    :param env: Jinja environment
    :type env: jinja environment
    :param defaults: Default configuration from all.yml
    :type defaults: Config
    :param scenario_name: The scenario name
    :type scenario_name: string
    :param scenarios_config: the scenario config
    :type scenarios_config: Config object
    """
    ini = getConfig('ini')
    sc_template = env.get_template('scenario.yml.tpl')
    stages = []
    for step_config in scenario_config['scenario_steps'].values():
        stage = (
            step_config['stage'] if (
                'stage' in step_config and
                ini['generator']['ci_dynamic_stages'] and
                ini['generator']['mode'] == 'dynamic')
            else defaults['gitlab']['git_projects'][step_config[
                'project']]['stage'])
        step_config['stage'] = stage
        if stage not in stages:
            stages.append(stage)
    output = sc_template.render(
        ci_mode=ini['generator']['mode'],
        ci_vault_input=ini['generator']['ci_vault_input'],
        ci_vault_file_var=ini['encryption']['key_env_name'],
        stages=stages,
        runner=defaults['runner'],
        scenario_name=scenario_name,
        scenario=scenario_config,
        git_projects=defaults['gitlab']['git_projects'],
        header=(f"Scenario {scenario_name} generated by chained-ci-py")
    )
    filename = (ini['generator']['ci_dynamic_file']
                if ini['generator']['mode'] == 'dynamic' else
                f"{ini['generator']['ci_included_folder']}/"
                f"{ini['generator']['ci_included_prefix']}"
                f"{scenario_name}.yml")
    with open(filename, 'w', encoding="utf-8") as scenario_file:
        scenario_file.write(output)
        log.info("Write scenario %s to '%s'", scenario_name, filename)


def _generate_main(env, defaults, scenarios_list, scenarios_config):
    """
    Generate scenarios into gitlab-ci.yml files.

    :param env: Jinja environment
    :type env: jinja environment
    :param defaults: Default configuration from all.yml
    :type defaults: Config
    :param scenarios_list: The scenario names
    :type scenarios_list: list of string
    :param scenarios_config: the scenario config
    :type scenarios_config: Config object
    """
    ini = getConfig('ini')
    ci_template = env.get_template('main.yml.tpl')
    output = ci_template.render(
        ci_mode=ini['generator']['mode'],
        yaml_image=ini['generator']['images']['yaml_checking'],
        ci_vault_input=ini['generator']['ci_vault_input'],
        ci_vault_file_var=ini['encryption']['key_env_name'],
        stages=defaults['stages'],
        runner=defaults['runner'],
        disable_pages=defaults['disable_pages'],
        scenarios=scenarios_list,
        scenarios_cfg=scenarios_config,
        ci_include_prefix=(f"{ini['generator']['ci_included_folder']}/"
                           f"{ini['generator']['ci_included_prefix']}"),
        header=("Generated by chained-ci-py")
    )
    with open(f"{ini['generator']['gitlabci_main']}", 'w',
              encoding="utf-8") as ci_file:
        ci_file.write(output)
        log.info("Write CI main file to '%s'",
                 f"{ini['generator']['gitlabci_main']}")


def generate(inv, scenario_name=None):
    """
    Generate scenarios into gitlab-ci.yml files.

    :param inv: Inventory generaterd from opts
    :type timeout: Inventory class
    :param scenario_name: The scenarion name from opts if 'dynamic' mode
    :type scenario_name: string
    """
    file_loader = FileSystemLoader(
        pkg_resources.resource_filename('chainedci', 'templates/'))
    env = Environment(autoescape=True, loader=file_loader)
    ini = getConfig('ini')
    if ini['generator']['mode'] == 'dynamic' and scenario_name:
        inv.load_config(scenario_name, None)
        _generate_scenario(env, inv.config['defaults'],
                           scenario_name,
                           inv.config['scenarios'][scenario_name])
    elif ini['generator']['mode'] == 'dynamic' and scenario_name is None:
        inv.load_config(list(inv.scenarios.keys())[0], None)
        _generate_main(env, inv.config['defaults'], [], {})
    else:
        scenarios = {}
        for scenario in inv.scenarios:
            inv.load_config(scenario, None)
            scenarios[scenario] = inv.config['scenarios'][scenario]
        if ini['generator']['mode'] == 'splitted':
            makedirs(ini['generator']['ci_included_folder'], exist_ok=True)
            for scenario in inv.scenarios:
                _generate_scenario(env, inv.config['defaults'],
                                   scenario, scenarios[scenario])
        _generate_main(env, inv.config['defaults'],
                       inv.scenarios, scenarios)


def run(opts, inv):
    """
    Run scenario step.

    :param opts: Options from command line
    :type timeout: Option class
    :param inv: Inventory generaterd from opts
    :type timeout: Inventory class
    """
    inv.load(opts.opts.scenario_name, opts.opts.job)
    scenario = Scenario(opts.opts.scenario_name)
    scenario.run(opts.opts.job)
