#!/usr/bin/python3

"""The simplebackup service script."""

import sys
import syslog
import threading
import time

from lib import common, constants, fs_operations, params_parsing, utils


# TODO: Refactoring https://gitlab.s-terra.com/vnovikov/simplebackup/-/merge_requests/158#note_262592
def get_upagent_params(params: utils.CommonParams) -> None:
    """
    Parse `ID`, `PASS` (via `UserPassword`) and `FTP_HOSTS` from UPAgent.

    Args:
        params: The parameters container to pass inside the program.
    """
    # Parse hosts list from UPAgent
    ftp_hosts = params_parsing.parse_uprun_hosts()
    if ftp_hosts:
        params.ftp_hosts = ftp_hosts
    else:
        utils.send_msg("Can't parse FTP hosts list. Assuming they didn't change.", syslog.LOG_WARNING)

    # Warn and return if UPAgent file doesn't exist, lacks read permissions, or is empty
    if not fs_operations.check_file(constants.UPAGENT_ETC_REG_TXT):
        utils.send_msg(
            f"Can't read '{constants.UPAGENT_ETC_REG_TXT}'. Assuming it didn't change.",
            syslog.LOG_WARNING,
        )
        return

    params_parsing.parse_config_file.cache_clear()  # To not get old cached values
    for param_name in ("ID", "UserPassword"):
        got_parameter, param_value = params_parsing.get_param_from_config_file(
            constants.UPAGENT_ETC_REG_TXT,
            constants.UPAGENT_GSU_SECTION,
            param_name,
            str,
        )

        # Warn if failed to parse the parameter
        if not got_parameter:
            utils.send_msg(
                f"Can't parse '{param_name}' from '{constants.UPAGENT_ETC_REG_TXT}'. Assuming it didn't change.",
                syslog.LOG_WARNING,
            )
            continue

        setattr(
            params,
            param_name.replace("UserPassword", "PASS").lower(),  # A workaround as we store `pass`, not `userpassword`
            param_value,
        )


def periodic_backup(backup_args: utils.ProcedureParams, syslog_tag: str) -> None:
    """
    Perform the backup procedure in an endless cycle.

    Args:
        backup_args: The parameters container with the single meaningful attribute:
          - `ret_code` (int): The return code of the the backup procedure.
        syslog_tag: The syslog tag.
    """  # The list is misaligned intentionally as LSP only renders this style in a readable way
    # Check that `/opt/simplebackup/tmp/` exists, and if it does not, create it
    if not fs_operations.check_or_create_dir(constants.BACKUPS_DIR):
        backup_args.ret_code = utils.RetCodes.TmpDirError
        return

    ret_code, parameters = params_parsing.get_parameters(is_backup_interval_needed=True)
    if not parameters:
        backup_args.ret_code = ret_code
        return

    # Check if file with backup list exists, has read permissions and is not empty
    if not fs_operations.check_file(constants.BACKUPLIST_FILE):
        backup_args.ret_code = utils.RetCodes.ListBackupError
        return

    # Send an info message indicating the start of an endless cycle
    utils.send_msg(f"The {syslog_tag} service is starting...", syslog.LOG_INFO)

    # Convert BACKUP_INTERVAL to seconds
    backup_interval_seconds = parameters.backup_interval * 60

    # Execute the script if we haven't received a stop signals
    while True:
        if utils.exit_flag:
            backup_args.ret_code = utils.RetCodes.InterruptError
            return

        # Check that backups directory exists and create it if it doesn't
        if not fs_operations.check_or_create_dir(constants.BACKUPS_DIR):
            utils.interruptible_sleep(backup_interval_seconds)
            continue

        # If the NO_UPLOAD parameter hasn't been selected, check UPAgent parameters if we're using UPAgent
        if not parameters.no_upload and parameters.use_ftp_settings_from_upagent:
            get_upagent_params(parameters)

        backup_args.ret_code = common.backup(parameters, common_script_timeout=parameters.common_script_timeout)

        if backup_args.ret_code == utils.RetCodes.Success:
            utils.send_msg(
                f"Periodic backup is completed and saved to '{parameters.backup_path}'.",
                syslog.LOG_INFO,
            )
        elif backup_args.ret_code == utils.RetCodes.BackupAreSameError:
            utils.send_msg(
                "Periodic backup is not created, because files being backed up have not changed.",
                syslog.LOG_INFO,
            )

        # Wait for `BACKUP_INTERVAL`
        utils.interruptible_sleep(backup_interval_seconds)


def main() -> utils.RetCodes:
    """
    Start the simplebackup service.

    Returns:
        RetCodes: The return code.
    """
    # Set the identification for syslog
    syslog_tag = constants.SYSLOG_TAG
    syslog.openlog(facility=syslog.LOG_LOCAL7, ident=syslog_tag)

    thread_args = utils.ProcedureParams()
    worker_thread = threading.Thread(target=periodic_backup, args=(thread_args, syslog_tag), daemon=True)
    worker_thread.start()
    while worker_thread.is_alive() and not utils.exit_flag:
        time.sleep(1)

    # If the exit flag is not set, exit with the actual return code
    if not utils.exit_flag:
        return thread_args.ret_code

    if not utils.wait_for_threads():
        ret_code = utils.RetCodes.ShutdownError
    elif thread_args.ret_code in (
        utils.RetCodes.BackupAreSameError,
        utils.RetCodes.InterruptError,  # To not become `failed` on exiting as we are handling it gracefully
    ):
        ret_code = utils.RetCodes.Success
    else:
        ret_code = thread_args.ret_code

    # Send an info message indicating interruption of the endless cycle
    utils.send_msg(f"The {syslog_tag} service was stopped.", syslog.LOG_INFO)
    return ret_code


if __name__ == "__main__":
    utils.setup_signal_handlers()

    sys.exit(main())
