"""Invirt build utilities.

This module contains utility functions used by both the invirtibuilder
and the remctl submission scripts that insert items into its queue.
"""


import os

from debian_bundle import changelog
from debian_bundle import deb822

import invirt.common as c
from invirt.config import structs as config


_QUEUE_DIR = '/var/lib/invirt-dev/queue'
_REPO_DIR = '/srv/git'
_LOG_DIR = '/var/log/invirt/builds'
_HOOKS_DIR = '/usr/share/invirt-dev/build.d'


class InvalidBuild(ValueError):
    pass


def getRepo(package):
    """Return the path to the git repo for a given package."""
    return os.path.join(_REPO_DIR, 'invirt/packages', '%s.git' % package)


def pocketToGit(pocket):
    """Map a pocket in the configuration to a git branch."""
    return getattr(getattr(config.build.pockets, pocket), 'git', pocket)


def pocketToApt(pocket):
    """Map a pocket in the configuration to an apt repo pocket."""
    return getattr(getattr(config.build.pockets, pocket), 'apt', pocket)


def getGitFile(package, ref, path):
    """Return the contents of a path from a git ref in a package."""
    return c.captureOutput(['git', 'cat-file', 'blob', '%s:%s' % (ref, path)],
                         cwd=getRepo(package))


def getChangelog(package, ref):
    """Get a changelog object for a given ref in a given package.

    This returns a debian_bundle.changelog.Changelog object for a
    given ref of a given package.
    """
    return changelog.Changelog(getGitFile(package, ref, 'debian/changelog'))


def getVersion(package, ref):
    """Get the version of a given package at a particular ref."""
    return getChangelog(package, ref).get_version()


def validateBuild(pocket, package, commit):
    """Given the parameters of a new build, validate that build.

    The checks this function performs vary based on whether or not the
    pocket is configured with allow_backtracking.

    A build of a pocket without allow_backtracking set must be a
    fast-forward of the previous revision, and the most recent version
    in the changelog most be strictly greater than the version
    currently in the repository.

    In all cases, this revision of the package can only have the same
    version number as any other revision currently in the apt
    repository if they have the same commit ID.

    If it's unspecified, it is assumed that pocket do not
    allow_backtracking.

    If this build request fails validation, this function will raise a
    InvalidBuild exception, with information about why the validation
    failed.

    If this build request can be satisfied by copying the package from
    another pocket, then this function returns that pocket. Otherwise,
    it returns True.
    """
    package_repo = getRepo(package)
    new_version = getVersion(package, commit)

    ret = True

    for p in config.build.pockets:
        if p == pocket:
            continue

        b = pocketToGit(p)
        current_commit = c.captureOutput(['git', 'rev-parse', b],
                                       cwd=package_repo)
        current_version = getVersion(package, b)

        if current_version == new_version:
            if current_commit == commit:
                ret = p
            else:
                raise InvalidBuild('Version %s of %s already available in '
                                   'pocket %s from commit %s' %
                                   (new_version, package, p, current_commit))

    if config.build.pockets[pocket].get('allow_backtracking', False):
        branch = pocketToGit(pocket)
        current_version = getVersion(package, branch)
        if new_version <= current_version:
            raise InvalidBuild('New version %s of %s is not newer than '
                               'version %s currently in pocket %s' %
                               (new_version, package, current_version, pocket))

        # Almost by definition, A is a fast-forward of B if B..A is
        # empty
        if not c.captureOutput(['git', 'rev-list', '%s..%s' % (commit, branch)]):
            raise InvalidBuild('New commit %s of %s is not a fast-forward of'
                               'commit currently in pocket %s' %
                               (commit, package, pocket))

    return ret
