#!/usr/bin/python3
#
# This file is part of OpenMediaVault.
#
# @license   http://www.gnu.org/licenses/gpl.html GPL Version 3
# @author    Volker Theile <volker.theile@openmediavault.org>
# @copyright Copyright (c) 2009-2017 Volker Theile
#
# OpenMediaVault is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
#
# OpenMediaVault is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with OpenMediaVault. If not, see <http://www.gnu.org/licenses/>.
import json
import os
import sys
import warnings
from typing import List

import click
import openmediavault.log
import salt.client
import salt.config
import salt.output

import openmediavault

__args = {'quiet': False}
__out = "highstate"
__deploy_scripts_dir = openmediavault.getenv(
    "OMV_SALT_SCRIPTS_DEPLOY_DIR", "/srv/salt/omv/deploy"
)
__stage_scripts_dir = openmediavault.getenv(
    "OMV_SALT_SCRIPTS_STAGE_DIR", "/srv/salt/omv/stage"
)
__salt_minion_config_file = openmediavault.getenv(
    "OMV_SALT_MINION_CONFIG_FILE", "/etc/salt/minion"
)
__dirty_modules_file = openmediavault.getenv(
    "OMV_ENGINED_DIRTY_MODULES_FILE",
    "/var/lib/openmediavault/dirtymodules.json"
)


warnings.filterwarnings("ignore", category=DeprecationWarning)


def get_dirty_modules() -> List[str]:
    with open(__dirty_modules_file, "r") as fd:
        names = json.load(fd)
    names.sort()
    return names


def reset_dirty_modules():
    with open(__dirty_modules_file, "w") as fd:
        fd.write("[]")


def process_names(ctx, param, value) -> List[str]:
    value = set(value)
    append_dirty = ctx.params.get("append_dirty", False)
    if append_dirty:
        value.update(get_dirty_modules())
    if not value:
        raise click.MissingParameter(ctx=ctx, param=param)
    return list(value)


@click.group()
def cli():
    pass


@cli.group()
def stage():
    pass


@stage.command(name='list', help='List available stages and exit.')
def stage_list():
    names = [
        name for name in os.listdir(__stage_scripts_dir)
        if os.path.isdir(os.path.join(__stage_scripts_dir, name))
    ]
    names.sort()
    print('\n'.join(names))
    sys.exit(0)


@stage.command(name='run', help='Run the specified stage.')
@click.argument('name')
@click.option(
    '--no-color',
    is_flag=True,
    help='Disable all colored output.'
)
@click.option(
    '-q',
    '--quiet',
    is_flag=True,
    help='Quiet mode, no messages are displayed.'
)
def stage_run(name, no_color, quiet):
    mopts = salt.config.minion_config(__salt_minion_config_file)
    caller = salt.client.Caller(mopts=mopts)
    result = caller.cmd("state.orchestrate", "omv.stage.{}".format(name))
    rc = result["retcode"]
    if not quiet:
        if no_color:
            mopts["color"] = False
        if __out == "nested":
            result = {"local": result}
        salt.output.display_output(result, out=__out, opts=mopts)
    sys.exit(rc)


@cli.group()
def deploy():
    pass


@deploy.command(name='list', help='List available states and exit.')
def deploy_list():
    names = [
        name for name in os.listdir(__deploy_scripts_dir)
        if os.path.isdir(os.path.join(__deploy_scripts_dir, name))
    ]
    names.sort()
    print('\n'.join(names))
    sys.exit(0)


@deploy.command(name='list-dirty', help='List states that are marked as dirty and exit.')
def deploy_list_dirty():
    names = get_dirty_modules()
    names.sort()
    if len(names) == 0:
        print("No dirty states found.")
    else:
        print('\n'.join(names))
    sys.exit(0)


@deploy.command(name='run', help='Run the specified state(s).')
@click.argument('names', nargs=-1, callback=process_names)
@click.option(
    '--no-color',
    is_flag=True,
    help='Disable all colored output.'
)
@click.option(
    '-q',
    '--quiet',
    is_flag=True,
    help='Quiet mode, no messages are displayed.'
)
@click.option(
    '--append-dirty',
    is_flag=True,
    help='Append the states that are marked as dirty.'
)
def deploy_run(names: List[str], no_color, quiet, append_dirty):
    # Check if the states exists.
    for name in names:
        path = os.path.join(__deploy_scripts_dir, name)
        if not os.path.exists(path):
            openmediavault.log.error("The state '%s' does not exist", name)
            sys.exit(100)
    # Execute the states.
    # https://docs.saltstack.com/en/latest/ref/runners/all/salt.runners.state.html#salt.runners.state.orchestrate
    names = list(map(lambda x: "omv.deploy.{}".format(x), names))
    mopts = salt.config.minion_config(__salt_minion_config_file)
    caller = salt.client.Caller(mopts=mopts)
    result = caller.cmd("state.orchestrate", names)
    rc = result["retcode"]
    # Print the output.
    if not quiet:
        if no_color:
            mopts["color"] = False
        if __out == "nested":
            result = {"local": result}
        salt.output.display_output(result, out=__out, opts=mopts)
    if append_dirty and rc == 0:
        reset_dirty_modules()
    sys.exit(rc)


def main():
    cli()
    return 0


if __name__ == '__main__':
    sys.exit(main())
