#!/usr/bin/python3

import os
import sys
import traceback
import argparse
from debian import debfile
from rhn import rhnLockfile
from spacewalk.server import rhnSQL
from spacewalk.common.rhnConfig import CFG, initCFG
from spacewalk.server.importlib.backendLib import DBstring, sanitizeValue
from spacewalk.satellite_tools.progress_bar import ProgressBar
from spacewalk.server import taskomatic

try:
    #  python 2
    from urllib import unquote
    import urlparse
except ImportError:
    #  python3
    import urllib.parse as urlparse # pylint: disable=F0401,E0611
    from urllib.parse import unquote

try:
    import xmlrpclib
except ImportError:
    import xmlrpc.client as xmlrpclib  # pylint: disable=F0401

DEFAULT_SERVER = 'localhost'

FORMAT_PRIORITY = ['.xz', '.gz', '']

PROCESSED = ['arch',
             'name',
             'summary',
             'epoch',
             'version',
             'release',
             'payload_size',
             'vendor',
             'package_group',
             'requires',
             'obsoletes',
             'predepends',
             'package',
             'architecture',
             'description',
             'maintainer',
             'section',
             'version',
             'depends',
             'provides',
             'conflicts',
             'replaces',
             'recommends',
             'suggests',
             'breaks',
             'pre-depends',
             'installed-size',
             ]

def systemExit(code, msg=None):
    sys.stderr.write(str(msg) + '\n')
    sys.exit(code)


def releaseLOCK():
    global LOCK
    if LOCK:
        LOCK.release()
        LOCK = None


def log(msg):
    print(msg)


class MetadataRefresh(object):

    def __init__(self, verbose):
        self.verbose = verbose
        self.channels = None

        self.select_channels = rhnSQL.prepare(
            """select distinct c.id, c.label from rhnChannel c, rhnChannelPackage cp, rhnPackage p, rhnPackageArch pa
                        where c.id=cp.channel_id and cp.package_id=p.id and p.package_arch_id=pa.id and pa.label like :deb""")
        self.select_pkgs = rhnSQL.prepare(
            """select p.id, p.path from rhnPackage p, rhnPackageArch pa
                        where p.package_arch_id=pa.id and pa.label like :deb""")
        self.insert_tag = rhnSQL.prepare("""
            insert into rhnPackageExtraTag(package_id, key_id, value) values(:package_id, :key_id, :value)
            """)
        self.delete_tags = rhnSQL.prepare("delete from rhnPackageExtraTag where package_id=:package_id")
        self.select_tag_key = rhnSQL.prepare("select id, name from rhnPackageExtraTagKey where name=:name");
        self.insert_tag_key = rhnSQL.prepare("insert into rhnPackageExtraTagKey(id, name) values(:id, :name)")

    def _create_extra_tag(self, package_id, key, value):
        value = sanitizeValue(value, DBstring(2048))
        self.select_tag_key.execute(name=key)
        dbkey = self.select_tag_key.fetchone_dict()
        if not dbkey:
            key_id = rhnSQL.Sequence('rhn_package_extra_tags_keys_id_seq').next()
            self.insert_tag_key.execute(id=key_id, name=key)
        else:
            key_id = dbkey["id"]
        self.insert_tag.execute(package_id=package_id, key_id=key_id, value=value)

    def _get_channels(self):
        if not self.channels:
            self.select_channels.execute(deb='%-deb')
            self.channels = self.select_channels.fetchall_dict()

    def refresh_db(self):
        self._get_channels()
        self.select_pkgs.execute(deb='%-deb')
        pkgs = self.select_pkgs.fetchall_dict()
        if pkgs:
            pb = ProgressBar(prompt='Updating {} packages in {} channel(s) '.format(len(pkgs), len(self.channels)), endTag=' - complete',
                         finalSize=len(pkgs), finalBarLength=40, stream=sys.stdout)
            pb.printAll(1)
            for pkg in pkgs:
                pb.addTo(1)
                pb.printIncrement()
                pkg_id = pkg['id']
                pkg_path = pkg['path']
                deb_path = os.path.join(CFG.MOUNT_POINT, pkg_path)
                deb = None
                try:
                    deb = debfile.DebFile(deb_path)
                    debcontrol = deb.debcontrol()
                    self.delete_tags.execute(package_id=pkg_id)

                    for key in list(debcontrol.keys()):
                        if key.lower() not in PROCESSED:
                            value = debcontrol[key]
                            if self.verbose:
                                log('%s |%s |%s | %s ' % (pkg_id, key, value, deb_path))
                            self._create_extra_tag(pkg_id, key, value)
                finally:
                    if deb:
                        deb.close()
            rhnSQL.commit()
            pb.printComplete()
        else:
            log('No packages to refresh')

    def regenerate_metadata(self):
        log('\nScheduling regeneration of repo metadata for channels:')
        self._get_channels()
        for channel in self.channels:
            log('- {}'.format(channel['label']))
        taskomatic.add_to_repodata_queue_for_channel_package_subscription(
            [channel['label'] for channel in self.channels], [], "server.app.updatepkgextratags")
        rhnSQL.commit()
        log('done.')


def main(args):
    global LOCK
    LOCK = None
    try:
        LOCK = rhnLockfile.Lockfile('/run/mgr-refresh-pkg-extra-tags.pid')
    except rhnLockfile.LockfileLockedException:
        systemExit(1, 'ERROR: attempting to run more than one instance of '
                      'mgr-refresh-pkg-metadata Exiting.')

    initCFG('server.satellite')
    initCFG('server.susemanager')
    rhnSQL.initDB()

    refresh = MetadataRefresh(args.verbose)
    refresh.refresh_db()
    refresh.regenerate_metadata()


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Update DEB packages extra fields in the database.')
    parser.add_argument('-v', '--verbose', dest='verbose', help='be verbose', action='store_true', default=False)
    args = parser.parse_args()

    try:
        sys.exit(abs(main(args) or 0))
    except KeyboardInterrupt:
        releaseLOCK()
        systemExit(1, '\nProcess has been interrupted.')
    except SystemExit as e:
        releaseLOCK()
        sys.exit(e.code)
    except:
        releaseLOCK()
        raise
