#!/usr/bin/python3

import argparse
import dnf
import itertools
import json
import operator
import os
import re
import sys

from collections import namedtuple
from lxml import etree

from dnf.cli.cli import BaseCli


CPE = 'cpe:/o:virtuozzohci:vz:2'
HCI_REGISTRY_TAG = 'hci_registry'
PKG_NAME = 'vstorage-ui-updater'
REINSTALL_PACKAGES = [
    'kernel',
]


Repo = namedtuple('Repo', ('id', 'baseurl', 'ver', 'registry'))


class PackageUpdate:

    def __init__(self, package):
        self.package = package

    def to_dict(self):
        return {
            'name': self.package.name,
            'version': fmt_ver(self.package),
            'epoch': self.package.epoch,
            'arch': self.package.arch,
            'size': self.package.size,
            'downloadsize': self.package.downloadsize,
        }


def first(iterable, default=None):
    return next(iter(iterable), default)


def parse_version(version):
    # returns version in list format, i.e. [3, 5, 0, 286]
    m = re.match("^(?:[a-z0-9-]+-)?(\d+)\.(\d+)\.(\d+) \((\d+)\)$", version)
    if m:
        return tuple(map(int, m.groups()))

    return None


def format_hci_version(version):
    return dict(zip(['major', 'middle', 'minor', 'release'], version))


def fmt_ver(p):
    return f'{p.version}-{p.release}'


def get_repomd_tag(repo, tag):
    path = os.path.join(repo._repo.getCachedir(), 'repodata', 'repomd.xml')
    try:
        repomd = etree.parse(path)
        hci_registry = repomd.find(
            f'//n:{tag}',
            namespaces={'n': 'http://linux.duke.edu/metadata/repo'}
        )
        return None if hci_registry is None else dict(hci_registry.attrib)
    except OSError as exc:
        # can be raised if repomd.xml was not found
        sys.stderr.write('Failed to get repository metadata: {}'.format(str(exc)))
        sys.exit(1)


def get_hci_repos(base):
    repos = []
    for name, repo in base.repos.items():
        tags = repo._repo.getDistroTags()

        ver = dict(tags).get(CPE)
        if ver is None:
            continue

        repos.append(
            Repo(
                id=name,
                baseurl=list(repo._repo.getMirrors() or repo.baseurl),
                ver=parse_version(ver),
                registry=get_repomd_tag(repo, tag=HCI_REGISTRY_TAG)
            )
        )
    return repos


def get_info(base, repos):
    packages = []
    updater = dict(installed=None, available=None)
    repo_ids = frozenset(r.id for r in repos)

    # query all packages: installed, available, old_available, reinstall
    sack = base.returnPkgLists()
    updater.update(
        installed=first(
           fmt_ver(p) for p in sack.installed if p.name == PKG_NAME
        )
    )
    reinstall = list(
        dict(name=p.name, version=fmt_ver(p)) for p in sack.reinstall_available
        if p.name in REINSTALL_PACKAGES
    )

    # filter updates only
    filtered = base.returnPkgLists(pkgnarrow='upgrades')
    updater.update(
        available=first(
            fmt_ver(p) for p in itertools.chain(sack.old_available,
                                                filtered.updates)
            if p.name == PKG_NAME
        )
    )
    packages = list(p for p in filtered.updates if p.repo.id in repo_ids)

    repos = sorted(repos, key=operator.attrgetter('ver'), reverse=True)
    registry = first(r.registry for r in repos if r.registry)

    repo = first(repos)
    return dict(
        repo=','.join(r for r in repo_ids),
        baseurl=repo.baseurl,
        version=format_hci_version(repo.ver),
        packages=list(
            dict(n) for n in frozenset(
                tuple(
                    PackageUpdate(p).to_dict().items()
                ) for p in packages
            )
        ),
        registry=registry,
        updater=updater,
        reinstall=reinstall
    )


def clean_cache(base):
    for r in base.repos.values():
        r._repo.expire()


def configure(reposdir=None, cachedir=None):
    base = BaseCli()
    base.conf.errorlevel = 0
    base.conf.debuglevel = 0
    if reposdir:
        base.conf.reposdir = reposdir
    if cachedir:
        base.conf.cachedir = cachedir
    base.setup_loggers()
    return base


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument("--reposdir")
    parser.add_argument("--cachedir")

    args = parser.parse_args()

    base = configure(args.reposdir)
    base.read_all_repos()

    clean_cache(base)
    try:
        base.fill_sack()
    except dnf.exceptions.RepoError as err:
        sys.stderr.write('Failed to get repository metadata: {}'.format(str(err)))
        sys.exit(1)

    hci_repos = get_hci_repos(base)
    if not hci_repos:
        sys.stderr.write('Cannot find repos to update from (reposdir: %s)'
                         % base.conf.reposdir)
        sys.exit(1)

    rv = get_info(base, hci_repos)
    print(json.dumps(rv, sort_keys=True, indent=2))
