#!/usr/bin/env python
# -*- coding: utf-8 -*-
##
#
# Vaisala software source code file
#
# Copyright (c) Vaisala Oyj 2017-2019. All rights reserved.
#
##

import argparse

from installer_utils import *

_logger = logging.getLogger('rsw-upgrade')
FROM_VERSIONS = [u"5.1.1"]
VERSION = u"5.1.2"
PACKAGES = [
    'vaisala-radarsw-webapp',
    'vaisala-radarsw-webapp-proxy',
    'vaisala-radarsw-monitoring',
    'vaisala-radarsw-backup',
    'vaisala-radarsw-nowcast',
    'vaisala-radarsw-warn-reader',
    'vaisala-radarsw-data-manager',
    'vaisala-radarsw-data-manager-input-service',
    'vaisala-radarsw-gis'
]
PACKAGES_REMOVE = []
PATCH_RELEASE = True

# Module / program level variable, initialized in main.
# Contains shared state by Yum repo steps.
REPO_STATE = None
BASE_REPO_SETUP_STEP = FunctionStep(lambda: base_repo_setup(REPO_STATE, _logger),
                                    u"Take CentOS base repository into use if needed")  # TODO: help_message
BASE_REPO_CLEANUP_STEP = FunctionStep(lambda: base_repo_cleanup(REPO_STATE, _logger),
                                      u"Reset Yum repositories to its original state")  # TODO: help_message


def warning_note(silent, currently_installed_version):
    if not silent and not currently_installed_version.startswith('5.'):
        while "the answer is invalid":
            text = u"""### NOTE: This upgrade script will remove all data manager raw data, MVF
configurations, and composite site configurations as part of the upgrade,
due to a data manager data format change for this release.
Press 'y' to continue, 'n' to quit: """
            reply = str(raw_input(text)).lower().strip()
            if reply[:1] == 'y':
                return True
            if reply[:1] == 'n':
                log_and_exit(u"Upgrade cancelled", _logger)


def check_installed_version(current_version, skip_version_check):
    dbg(u"Identifying the currently installed version of IRIS Focus.")
    if not current_version.strip():
        log_and_exit(u"Can not upgrade because no previous version of IRIS Focus is installed." +
                     u"\nUpgrade is only possible from versions {}".format(", ".join(FROM_VERSIONS)), _logger)
    if not skip_version_check and current_version not in FROM_VERSIONS:
        log_and_exit(u"Can not upgrade from {} to {}".format(current_version, VERSION), _logger)


def create_prerequisite_steps(args, currently_installed_version):
    steps = [
        PreFunctionStep(lambda: warning_note(args.silent, currently_installed_version), u"User Confirmation"),
        PreFunctionStep(lambda: ensure_run_by_root(), u"Check if invoked by root user"),
        PreFunctionStep(lambda: ensure_correct_centos_version(args.skip_os_version_check),
                        u"Check if correct operating system version"),
        PreFunctionStep(lambda: ensure_good_path_environment(_logger), u"Configure PATH"),
        PreFunctionStep(lambda: check_installed_version(currently_installed_version, args.skip_version_check),
                        u"Check IRIS Focus version"),
        PreFunctionStep(lambda: log_system_information(_logger), u"Log system information"),
        create_base_repo_available_step(REPO_STATE, _logger),
        create_hostname_resolvable_step(_logger),
        make_verify_executable_is_callable_step(u"rsw-rpm-radarsw-repo-create", _logger)
    ]

    return steps


def create_patch_prerequisite_steps(args, currently_installed_version):
    steps = [
        PreFunctionStep(lambda: ensure_run_by_root(), u"Check if invoked by root user"),
        PreFunctionStep(lambda: ensure_correct_centos_version(args.skip_os_version_check),
                        u"Check if correct operating system version"),
        PreFunctionStep(lambda: check_installed_version(currently_installed_version, args.skip_version_check),
                        u"Check IRIS Focus version"),
        PreFunctionStep(lambda: log_system_information(_logger), u"Log system information")
    ]

    return steps


def create_post_steps():
    return [
        BASE_REPO_CLEANUP_STEP,
        create_remove_yum_cache_step(_logger)
    ]


def create_steps(currently_installed_version):
    initial_steps = [
        BASE_REPO_SETUP_STEP,
        ShellCommandStep(["/usr/vaisala/radarsw/backup/bin/do-backups"],
                         u"Back up configuration and database",
                         help_message=u"Could not make backups. For more information look at file " +
                                      u"'/var/log/vaisala/radarsw/backup.log'.",
                         logger=_logger),
        ShellCommandStep(["rsw-rpm-radarsw-repo-create"], u"Install 'radarsw' Yum repo", logger=_logger),
        ShellCommandStep(["rsw-package-available", VERSION] + PACKAGES,
                         u"Check if rpm packages are available", logger=_logger),

        ShellCommandStep(["service", "monit", "stop"], u"Stop service monitoring daemon monit",
                         logger=_logger),
        ShellCommandStep(["service", "vaisala-radarsw-webapp", "stop"], u"Stop web application service",
                         logger=_logger),
        ShellCommandStep(["service", "vaisala-radarsw-nowcast-server", "stop"], u"Stop Nowcast server",
                         logger=_logger),
        ShellCommandStep(["service", "vaisala-radarsw-warn-reader", "stop"], u"Stop Warn Reader service",
                         logger=_logger),
        ShellCommandStep(["service", "vaisala-radarsw-data-manager", "stop"], u"Stop Data Manager service",
                         logger=_logger),
        ShellCommandStep(["service", "vaisala-radarsw-data-manager-input-service", "stop"],
                         u"Stop Data Manager input service",
                         logger=_logger)
    ]

    install_new_packages_steps = [
       ShellCommandStep(['yum', '-y', 'install'] + PACKAGES, u"Install RPM packages",
                        help_message="""Couldn't install packages. If you encountered a problem with Yum repos being
                        offline, try disabling all repos that have 0 packages available or when you
                        encounter an error such as 'Cannot find a valid baseurl for repo: <repo-id>'.

                        You can list all enabled repos with command
                         - yum repolist enabled

                        You'll see the package count on the rightmost column. You can disable the repo
                        by calling
                         - yum-config-manager --disable <repo-id>
                        where <repo-id> is the left-most column in the previous listing.""", logger=_logger),
    ]

    db_migrate_steps = [
        ShellCommandStep(["rsw-db-tool", "migrate"], "Migrate WX database for IRIS Focus", logger=_logger)
    ]

    db_steps = [
        ShellCommandStep(["rsw-db-tool", "migrate"], "Migrate WX database for IRIS Focus", logger=_logger),
        ShellCommandStep(["rsw-vsp-db-tool", "migrate"], "Migrate VSP database for IRIS Focus",
                         logger=_logger),
        ShellCommandStep(["rsw-data-manager-clear-data", "--silent"],
                         "Clear data for Data Manager",
                         logger=_logger),
        ShellCommandStep(["rsw-data-manager-db-tool", "migrate"], "Migrate database for Data Manager",
                         logger=_logger),
        ShellCommandStep(["service", "vaisala-radarsw-data-manager", "start"], "Start up Data Manager",
                         logger=_logger),
        ShellCommandStep(["service", "vaisala-radarsw-webapp", "start"], u"Start web application service",
                         logger=_logger),
        FunctionStep(lambda: wait_until_code(200, "localhost:34080", "/health", "IRIS Focus", _logger),
                     "Wait for IRIS Focus to start up after socket server configuration")
    ]

    start_services_steps = [
        ShellCommandStep(["service", "monit", "start"], u"Restart service monitoring daemon monit",
                         logger=_logger),
        ShellCommandStep(["service", "vaisala-radarsw-warn-reader", "start"], u"Start Warn Reader",
                         logger=_logger),
        ShellCommandStep(["service", "vaisala-radarsw-nowcast-server", "start"], u"Start Nowcast server",
                         logger=_logger),
        ShellCommandStep(["service", "vaisala-radarsw-data-manager-input-service", "start"],
                         u"Start Data Manager Input Service", logger=_logger)
    ]

    if currently_installed_version.startswith('5.1'):
        # Version 5.1.1 patch only requires webapp replacement.
        steps = initial_steps + install_new_packages_steps + start_services_steps + create_post_steps()
    elif currently_installed_version.startswith('5.0'):
        # Version 5.1 requires a db migration.
        steps = initial_steps + install_new_packages_steps + db_migrate_steps + start_services_steps + \
                create_post_steps()
    else:
        # Version 4.0.1 changed the DM and thus requires the DM data to be removed.
        steps = initial_steps + install_new_packages_steps + db_steps + start_services_steps +\
                create_post_steps()

    return steps


def create_patch_steps():
    copy_version_file_step = [
        ShellCommandStep(['install', '-m', '644', '-o', 'root', '-g', 'root',
                          './version', '/usr/vaisala/radarsw'],
                         u"Install version file", logger=_logger)
    ]

    copy_files_steps = [
        ShellCommandStep(['install', '-m', '755', '-o', 'root', '-g', 'root',
                          './rsw-postgresql-configure-settings', '/usr/bin'],
                         u"Install rsw-postgresql-configure-settings file", logger=_logger)
    ]

    post_copy_steps = [
        ShellCommandStep(["/usr/vaisala/radarsw/backup/bin/do-backups"],
                         u"Back up configuration and database",
                         help_message=u"Could not make backups. For more information look at file " +
                                      u"'/var/log/vaisala/radarsw/backup.log'.",
                         logger=_logger),

        ShellCommandStep(["systemctl", "stop", "monit"], u"Stop service monitoring daemon monit",
                         logger=_logger),
        ShellCommandStep(["systemctl", "stop", "vaisala-radarsw-webapp"], u"Stop web application service",
                         logger=_logger),
        ShellCommandStep(["systemctl", "stop", "vaisala-radarsw-nowcast-server"], u"Stop Nowcast server",
                         logger=_logger),
        ShellCommandStep(["systemctl", "stop", "vaisala-radarsw-warn-reader"], u"Stop Warn Reader service",
                         logger=_logger),
        ShellCommandStep(["systemctl", "stop", "vaisala-radarsw-data-manager"], u"Stop Data Manager service",
                         logger=_logger),
        ShellCommandStep(["systemctl", "stop", "vaisala-radarsw-data-manager-input-service"],
                         u"Stop Data Manager input service",
                         logger=_logger),
        ShellCommandStep(["systemctl", "stop", "vaisala-radarsw-geoserver"],
                         u"Stop geoserver service", logger=_logger),
        ShellCommandStep(["systemctl", "stop", "postgresql-9.4.service"],
                         u"Stop postgresql database service", logger=_logger),

        ShellCommandStep(['install', '-m', '700', '-o', 'postgres', '-g', 'postgres',
                          '-d', '/srv/vaisala/radarsw/pgsql'],
                         u"Create postgres pgsql dir on /srv with proper permissions", logger=_logger),
        ShellCommandStep(['install', '-m', '700', '-o', 'postgres', '-g', 'postgres',
                          '-d', '/srv/vaisala/radarsw/pgsql/9.4'],
                         u"Create postgres 9.4 dir on /srv with proper permissions", logger=_logger),
        ShellCommandStep(["rsync", "-av", "/var/lib/pgsql/9.4/data", "/srv/vaisala/radarsw/pgsql/9.4"],
                         u"Copy postgres data files to /srv", logger=_logger),
        ShellCommandStep(["mv", "/var/lib/pgsql/9.4/data", "/var/lib/pgsql/9.4/data.bak"],
                         u"Rename old postgres data files to /var/lib/pgsql/9.4/data.bak", logger=_logger),
        ShellCommandStep(['rsw-postgresql-configure-settings', '--postgresql-conf-file',
                          '/srv/vaisala/radarsw/pgsql/9.4/data/postgresql.conf'],
                         u"Modify postgresql config file to change data dir to /srv", logger=_logger),
        ShellCommandStep(["sed", "-i", "/PGDATA=\//c\Environment=PGDATA=/srv/vaisala/radarsw/pgsql/9.4/data",
                          "/usr/lib/systemd/system/postgresql-9.4.service"],
                         u"Point Postgres unit to different default data directory on /srv", logger=_logger),
        ShellCommandStep(["systemctl", "daemon-reload"], u"Reload systemctl services", logger=_logger),
        ShellCommandStep(["semanage", "fcontext", "-a", "-t", "postgresql_db_t",
                          "/srv/vaisala/radarsw/pgsql(/.*)?"],
                         u"Change new postgres data dir context to be managed by SELinux", logger=_logger),
        ShellCommandStep(["restorecon", "-R", "-v", "/srv/vaisala/radarsw/pgsql"],
                         u"Add SELinux context mapping for new postgres data dir", logger=_logger),
        ShellCommandStep(["systemctl", "start", "postgresql-9.4.service"],
                         u"Start postgresql database service", logger=_logger),

        ShellCommandStep(["systemctl", "start", "postgresql-9.4.service"],
                         u"Start postgresql database service", logger=_logger),
        ShellCommandStep(["systemctl", "start", "vaisala-radarsw-geoserver"],
                         u"Start geoserver service", logger=_logger),
        ShellCommandStep(["systemctl", "start", "vaisala-radarsw-data-manager-input-service"],
                         u"Start Data Manager input service",
                         logger=_logger),
        ShellCommandStep(["systemctl", "start", "vaisala-radarsw-data-manager"],
                         u"Start Data Manager service", logger=_logger),
        ShellCommandStep(["systemctl", "start", "vaisala-radarsw-warn-reader"], u"Start Warn Reader service",
                         logger=_logger),
        ShellCommandStep(["systemctl", "start", "vaisala-radarsw-nowcast-server"], u"Start Nowcast server",
                         logger=_logger),
        ShellCommandStep(["systemctl", "start", "vaisala-radarsw-webapp"], u"Start web application service",
                         logger=_logger),
        ShellCommandStep(["systemctl", "start", "monit"], u"Start service monitoring daemon monit",
                         logger=_logger)
    ]

    return copy_version_file_step + copy_files_steps + post_copy_steps


# noinspection PyArgumentList
def log(*args, **kwargs):
    return _logger.info(*args, **kwargs)


# noinspection PyArgumentList
def dbg(*args, **kwargs):
    return _logger.debug(*args, **kwargs)


def _log_title(title):
    log(u"#" * LINE_LENGTH)
    log(u"# " + title)
    log(u"#" * LINE_LENGTH)


def setup_logging(debug, dry_run):
    _logger.setLevel(logging.DEBUG)

    sout_h = logging.StreamHandler(sys.stdout)
    if debug:
        sout_h.setLevel(logging.DEBUG)
    else:
        sout_h.setLevel(logging.INFO)
    _logger.addHandler(sout_h)

    log_file = os.path.join(os.environ['HOME'], u"rsw-upgrade-{0}.log".format(time.strftime("%Y%m%d-%H%M%S")))
    file_h = logging.FileHandler(log_file)
    file_h.setLevel(logging.DEBUG)
    _logger.addHandler(file_h)

    if not dry_run:
        log(u"Logging to stdout and to '{0}'.".format(log_file))
    return log_file


def create_arg_parser():
    parser = argparse.ArgumentParser('rsw-upgrade',
                                     formatter_class=argparse.ArgumentDefaultsHelpFormatter)

    # Optional arguments
    opt_group = parser.add_argument_group("optional arguments")
    opt_group.add_argument("-d", "--debug", dest="debug", action="store_true", help="Enable debug messages")
    opt_group.add_argument("--dry-run", dest="dry_run", action="store_true", default=False,
                           help="Don't do anything, just list the steps that would be run.")
    opt_group.add_argument("--skip-os-version-check", dest="skip_os_version_check", default=False,
                           action="store_true",
                           help=u"Skip operating system version check. " +
                                u"IRIS Focus is officially supported only on CentOS 7.4 and 7.6.")
    opt_group.add_argument("--skip-version-check", dest="skip_version_check", default=False,
                           action="store_true",
                           help=u"Skip IRIS Focus version check and force upgrade. Used for testing.")
    opt_group.add_argument("--silent", dest="silent", default=False, action="store_true",
                           help="Run upgrade without prompts")

    if PATCH_RELEASE:
        return parser

    repo_group = parser.add_mutually_exclusive_group(required=True)
    repo_group.add_argument("--online", dest="online", default=False, action="store_true",
                            help="Allow online CentOS base repository")
    repo_group.add_argument("--offline", dest="offline", default=False, action="store_true",
                            help="Disable online CentOS base repository and require a " +
                                 "file://-protocol CentOS base repository")

    return parser


def main():
    parser = create_arg_parser()
    args = parser.parse_args()
    log_file = setup_logging(args.debug, args.dry_run)
    currently_installed_version = resolve_webapp_version()

    if PATCH_RELEASE:
        pre_steps = create_patch_prerequisite_steps(args, currently_installed_version)
        steps = create_patch_steps()
    else:
        global REPO_STATE
        REPO_STATE = RepoState(args.online)
        pre_steps = create_prerequisite_steps(args, currently_installed_version)
        steps = create_steps(currently_installed_version)

    status = u"FAILED"
    try:
        _log_title(u"Pre-upgrade checks")
        StepExecutor(pre_steps, _logger, args.dry_run).execute()
        _log_title(u"Upgrade")
        StepExecutor(steps, _logger, args.dry_run).execute()
        status = u"SUCCESSFUL"
    finally:
        if args.dry_run:
            _log_title(u"# Dry run complete")
        else:
            log(u"\n" + (u"#" * LINE_LENGTH))
            log(u"# Upgrade " + status + u". Log: {}".format(log_file))
            log(u"#" * LINE_LENGTH)


if __name__ == '__main__':
    try:
        main()
    finally:
        try:
            remove_radarsw_repo(_logger)
        except:
            pass
