#!/usr/bin/python2

import argparse
import sys
import os

from spacewalk.common.rhnConfig import CFG, initCFG
from spacewalk.common.fileutils import cleanupAbsPath
from spacewalk.cdn_tools.common import CustomChannelSyncError, CdnMappingsLoadError, CountingPackagesError
from spacewalk.cdn_tools.cdnsync import CdnSync
from spacewalk.server import rhnSQL
from spacewalk.common.usix import raise_with_tb
from rhn import rhnLockfile
from rhn.connections import idn_ascii_to_puny
from spacewalk.server.importlib.importLib import InvalidArchError, \
    InvalidChannelError, InvalidChannelFamilyError, MissingParentChannelError


def system_exit(code, msgs=None):
    """Exit with a code and optional message(s). Saved a few lines of code."""

    if msgs:
        if type(msgs) not in [type([]), type(())]:
            msgs = (msgs, )
        for msg in msgs:
            sys.stderr.write(str(msg) + '\n')
    sys.exit(code)


def process_commandline():
    """process the commandline, setting the CFG object"""

    initCFG('server.satellite')
    CFG.ENABLE_NVREA = 1
    parser = argparse.ArgumentParser()
    parser.add_argument("-l", "--list-channels", action="store_true", help="List channels available to sync.")
    parser.add_argument("-r", "--show-repos", action="store_true",
                        help="Show all repositories assigned to channels for debug purposes. Use together with "
                             "--list-channels or --cdn-certs.")
    parser.add_argument("-c", "--channel", action="append", help="Sync this channel only.")
    parser.add_argument("-m", "--mount-point", action="store", help="Source mount point for import from Content ISO")
    parser.add_argument("--consider-full", action="store_true",
                        help="Mount point will be considered to contain full channels.")
    parser.add_argument("-a", "--add-repo", action="append", help="Attach specified CDN repository to custom channel.")
    parser.add_argument("-d", "--delete-repo", action="append",
                        help="Delete specified CDN repository from custom channel.")
    parser.add_argument("--email", action="store_true", help="e-mail a report of what was synced/imported")
    parser.add_argument("--traceback-mail", action="store",
                        help="alternative email address(es) for sync output (--email option)")
    parser.add_argument("--no-packages", action="store_true", help="Do not sync packages.")
    parser.add_argument("--no-errata", action="store_true", help="Do not sync errata.")
    parser.add_argument("--no-kickstarts", action="store_true", help="Do not sync kickstart repositories.")
    parser.add_argument("--force-all-errata", action="store_true", help="Process metadata of all errata, "
                                                                        "not only missing.")
    parser.add_argument("--force-kickstarts", action="store_true", help="Overwrite kickstart files.")
    parser.add_argument("--clear-cache", action="store_true", help="Delete partially synced channels.")
    parser.add_argument('--http-proxy', action='store', help="alternative http proxy (hostname:port)")
    parser.add_argument('--http-proxy-username', action='store', help="alternative http proxy username")
    parser.add_argument('--http-proxy-password', action='store', help="alternative http proxy password")
    parser.add_argument('-p', '--print-configuration', action='store_true', help='print the configuration and exit')
    parser.add_argument('--count-packages', action='store_true', help="Count number of packages in all "
                        "repositories for every channel")
    parser.add_argument("--no-rpms", action="store_true", help="Do not keep RPMs on disk after DB import (debug only)")
    parser.add_argument("--batch-size", action="store", help="max. batch size for package import (debug only)")
    parser.add_argument("-v", "--verbose", action='count', help="Verbose output. Possible to accumulate: -vvv")
    parser.add_argument("--cdn-certs", action="store_true",
                        help="Print details about currently used SSL certificates for accessing CDN.")
    parser.add_argument("--list-eol", action="store_true",
                        help="List end-of-life info about (some) channels available to sync.")

    cmd_args = parser.parse_args()

    if cmd_args.print_configuration:
        CFG.show()
        sys.exit(0)

    if cmd_args.http_proxy:
        try:
            int(cmd_args.http_proxy.split(':')[1])
        except ValueError:
            system_exit(1, "Incorrect proxy port number: %s" % cmd_args.http_proxy.split(':')[1])
        CFG.set("HTTP_PROXY", idn_ascii_to_puny(cmd_args.http_proxy))
        CFG.set("HTTP_PROXY_USERNAME", cmd_args.http_proxy_username)
        CFG.set("HTTP_PROXY_PASSWORD", cmd_args.http_proxy_password)

    CFG.set("TRACEBACK_MAIL", cmd_args.traceback_mail or CFG.TRACEBACK_MAIL)

    if cmd_args.mount_point:
        cmd_args.mount_point = cleanupAbsPath(cmd_args.mount_point)
        if not os.path.isdir(cmd_args.mount_point):
            system_exit(1, "Invalid mount point: %s" % cmd_args.mount_point)

    if cmd_args.channel:
        cmd_args.channel = set(cmd_args.channel)

    if cmd_args.batch_size:
        try:
            batch_size = int(cmd_args.batch_size)
            if batch_size <= 0:
                raise ValueError()
        except ValueError:
            system_exit(1, "Invalid batch size: %s" % cmd_args.batch_size)

    return cmd_args


def getDbIssParent():
    rhnSQL.initDB()
    sql = "select label from rhnISSMaster where is_current_master = 'Y'"
    h = rhnSQL.prepare(sql)
    h.execute()
    row = h.fetchone_dict()
    if not row:
        return None
    return row['label']


if __name__ == '__main__':
    LOCK = None
    try:
        # quick check to see if you are a super-user.
        if os.getuid() != 0:
            sys.stderr.write("ERROR: must be root to execute\n")
            sys.exit(8)

        # acquire lock/check for other instances of cdn-sync
        #   i.e., lock against multiple instances of cdn-sync
        LOCK = rhnLockfile.Lockfile('/var/run/cdn-sync.pid')

        args = process_commandline()

        if CFG.get('disconnected') == 1:
            system_exit(1, 'ERROR: the Satellite server is installed in disconnected mode, cannot continue syncing '
                        'from CDN')

        if getDbIssParent() or (CFG.get('iss_parent') and CFG.get('disable_iss') == 0):
            parent = getDbIssParent()
            if not parent:
                parent = CFG.get('iss_parent')
            system_exit(1, 'ERROR: the Satellite server is registered to a parent Satellite "%s".\nTo sync content '
                        'from the parent Satellite via Inter-Satellite-Sync, please, use the "satellite-sync" command' %
                        parent)

        cdnsync = CdnSync(no_packages=args.no_packages, no_errata=args.no_errata,
                          no_rpms=args.no_rpms, no_kickstarts=args.no_kickstarts,
                          log_level=args.verbose, mount_point=args.mount_point,
                          consider_full=args.consider_full,
                          force_all_errata=args.force_all_errata,
                          force_kickstarts=args.force_kickstarts,
                          email=args.email, import_batch_size=args.batch_size)

        error_messages = []
        if args.list_channels:
            if args.count_packages:
                cdnsync.count_packages(channels=args.channel)
            cdnsync.print_channel_tree(repos=args.show_repos)
        elif args.count_packages:
            cdnsync.count_packages(channels=args.channel)
        elif args.clear_cache:
            cdnsync.clear_cache()
        elif args.cdn_certs:
            cdnsync.print_cdn_certificates_info(repos=args.show_repos)
        elif args.list_eol:
            cdnsync.print_eol_channel_list()
        elif args.add_repo or args.delete_repo:
            error_messages = cdnsync.setup_repos_and_sync(channels=args.channel, add_repos=args.add_repo,
                                                          delete_repos=args.delete_repo)
        else:
            error_messages = cdnsync.sync(channels=args.channel)

        cdnsync.send_email(additional_messages=error_messages)

        if error_messages:
            system_exit(1, error_messages)

        LOCK.release()

    except KeyboardInterrupt:
        system_exit(1, "\nProcess has been interrupted.")
    except SystemExit:
        e = sys.exc_info()[1]
        if LOCK:
            LOCK.release()
        sys.exit(e.code)
    except rhnLockfile.LockfileLockedException:
        system_exit(1, "SYNC ERROR: attempting to run more than one instance of cdn-sync. Exiting.")
    except CustomChannelSyncError:
        e = sys.exc_info()[1]
        if LOCK:
            LOCK.release()
        system_exit(13, ["ERROR: custom CDN repository sync failed: ", e])
    except (MissingParentChannelError, InvalidChannelFamilyError,
            InvalidChannelError, InvalidArchError):
        e = sys.exc_info()[1]
        if LOCK:
            LOCK.release()
        system_exit(1, "SYNC ERROR: %s. Exiting." % str(e))
    except rhnSQL.SQLError:
        e = sys.exc_info()[1]
        if LOCK:
            LOCK.release()
        system_exit(20, ["DATABASE ERROR: %s. " % str(e), "Check if your database is running."])
    except CdnMappingsLoadError:
        e = sys.exc_info()[1]
        system_exit(30, ["ERROR: %s" % str(e)])
    except IOError:
        e = sys.exc_info()[1]
        if LOCK:
            LOCK.release()
        # Broken pipe
        if e.errno != 32:
            raise_with_tb(Exception("SYNC ERROR: attempting to display as much information as possible\n %s" % str(e)),
                          sys.exc_info()[2])
    except CountingPackagesError:
        e = sys.exc_info()[1]
        if LOCK:
            LOCK.release()
        system_exit(20, "ERROR: Problem occurred during package counting: %s" % str(e))
    except EOFError:
        e = sys.exc_info()[0]
        if LOCK:
            LOCK.release()
        system_exit(40, "SYNC ERROR: Perhaps checksum_cache is corrupted? Remove /var/cache/rhn/reposync/checksum_cache and retry.\n%s" % str(e))

    except Exception:
        e = sys.exc_info()[1]
        if LOCK:
            LOCK.release()
        raise_with_tb(Exception("SYNC ERROR: attempting to display as much information as possible\n %s" % str(e)),
                      sys.exc_info()[2])
else:
    raise ImportError("module cannot be imported")
