#!/usr/bin/env python3
"""hos-delta cli module: CLI tool"""

import logging
import os
import sys

from hos_delta import args as hos_delta_args
from hos_delta import delta
from hos_delta import release
from hos_delta import rootfs
from hos_delta import utils
from hos_delta import version

LOG = logging.getLogger()

def _detect_rootfs(releases_path, release_version, image, machine):
    release_path = os.path.join(releases_path, release_version)
    try:
        rootfs_path = release.find_image_rootfs(release_path, image, machine)
    except LookupError:
        LOG.error("Couldn't find the rootfs for image %s in release %s.", image,
                  release_version)
        sys.exit(1)
    try:
        fingerprint_path = release.find_image_fingerprint(release_path, image, machine)
    except LookupError:
        LOG.error("Couldn't find the fingerprint for image %s in release %s", image,
                  release_version)
        sys.exit(1)
    return rootfs.RootFS(rootfs_path, fingerprint_path)

def _find_releases(releases_path, target_version):
    releases_version = []
    found_final_version = False
    for release_version in next(os.walk(releases_path))[1]:
        if version.is_valid(release_version):
            if release_version == target_version:
                found_final_version = True
            releases_version.append(release_version)

    return (releases_version, found_final_version)

def _generate(baseline, releases, target, args, LOG):
    LOG.info("Generating delta from %s to %s... please wait...", baseline.version, target.version)
    try:
        target.generate_delta(baseline, releases, not args.novalidation, args.delta_precentage_limit, LOG)
    except LookupError:
        LOG.error('Could not find the rootfs for the requested version %s', target.version)
        sys.exit(1)
    except FileExistsError:
        LOG.error("Delta already in place. If you want to regenerate, remove it manually.")
        sys.exit(1)
    except ValueError as exc:
        LOG.error("Delta validation failed: %s.", str(exc))
        sys.exit(1)
    except OverflowError:
        LOG.error("The generated delta exceeds the set delta precentage limit (%s%%).",
                  args.delta_precentage_limit)
        sys.exit(1)
    LOG.info("Done")

def generate(args):
    """Handler for the delta generation subcommand"""
    if not os.path.isdir(args.releases_path):
        LOG.error("Can't access the releases path provided.")
        sys.exit(1)

    if not version.is_valid(args.version):
        LOG.error("The version provided is not parsable.")
        sys.exit(1)

    # Find all the releases in the releases path and make sure the requested
    # version is part of them.
    releases_version, _found_final_version = _find_releases(args.releases_path, args.version)
    if not _found_final_version:
        LOG.error("Can't find version specified in the releases path.")
        sys.exit(1)

    # Sort releases version so we can detect later on the baseline easier.
    releases_version.sort(key=version.version_key)

    # Remove all the versions greater or equal than the requested version (the one we update to).
    releases_version = [x for x in releases_version
                if version.version_key(x) < version.version_key(args.version)]

    # Detect the baseline
    baseline_version = release.find_baseline_release(args.image, args.machine,
                                                     args.releases_path, releases_version)
    if not baseline_version:
        LOG.error("Can't find baseline.")
        sys.exit(1)
    LOG.info("Found baseline in release %s .", baseline_version)

    # We filter out releases so it contains all the versions in between
    # baseline and the requested version (we already removed above versions
    # equal or greater than the requested version).
    releases_version = [x for x in releases_version
                if version.version_key(x) > version.version_key(baseline_version)]
    _releases_str = baseline_version
    for release_version in releases_version:
        _releases_str += ', ' + release_version
    LOG.debug("The delta will support the following releases %s to update to %s .",
              _releases_str, args.version)

    # Alias for the target version to maintain consistency with baseline and
    # releases.
    target_version = args.version

    LOG.info("Parsing releases root filesystem...")
    baseline = _detect_rootfs(args.releases_path, baseline_version, args.image, args.machine)
    releases = []
    for release_version in releases_version:
        releases.append(_detect_rootfs(args.releases_path, release_version, args.image,
                                       args.machine))
    target = _detect_rootfs(args.releases_path, target_version, args.image, args.machine)
    LOG.info("Parsing done.")

    LOG.debug("The delta/rootfs ratio threshold set to %f%%.", args.delta_precentage_limit)

    _generate(baseline, releases, target, args, LOG)

def mgenerate(args):
    """Handler for the manual delta generation subcommand"""
    try:
        baseline = rootfs.RootFS(args.base_rootfs_path, args.base_fingerprint_path)
    except FileNotFoundError:
        LOG.error("Invalid arguments for base.")
        sys.exit(1)
    try:
        target = rootfs.RootFS(args.target_rootfs_path, args.target_fingerprint_path)
    except FileNotFoundError:
        LOG.error("Invalid arguments for target.")
        sys.exit(1)

    _generate(baseline, [], target, args, LOG)

def dapply(args):
    """Handler for the delta apply subcommand"""
    try:
        ddd = delta.Delta(args.delta_path)
        ddd.apply(args.active_path, args.inactive_path, LOG)
    except ImportError as exc:
        LOG.error("Applying delta failed: %s.", str(exc))
        sys.exit(1)
    except ValueError as exc:
        LOG.error("Applying delta failed: %s.", str(exc))
        sys.exit(1)
    except LookupError:
        LOG.error("The delta path doesn't seem to actually have a delta payload.")
        sys.exit(1)
    LOG.info("Delta applied successfully.")

    if args.novalidation:
        LOG.warning("No root filesystem validation done as requested.")
    else:
        __validate(args.inactive_path)

def __validate(path):
    try:
        fingerprint = utils.search_file(path, 'fingerprint', ['json', 'json.gz'])
    except LookupError:
        LOG.error("Can't find the fingerprint in the root mountpoint provided.")
        sys.exit(1)
    root = rootfs.RootFS(path, fingerprint)
    LOG.info("Validating %s - version %s...", path, root.version)
    try:
        root.validate()
        LOG.info("Validation succeeded.")
    except AssertionError as exc:
        LOG.error("Validation failed: %s.", str(exc))
        sys.exit(1)

def validate(args):
    """Handler for the root filesystem validation subcommand"""
    __validate(args.rootfs_path)

def main():
    """ Main function for CLI """
    # Parse arguments
    parsed_args = hos_delta_args.get_args()

    # Setup logger
    if parsed_args.verbose:
        log_level = logging.DEBUG
    else:
        log_level = logging.INFO
    logging.basicConfig(format='[%(levelname)s] %(message)s', level=log_level)

    # We need a subcommand
    if not parsed_args.command:
        LOG.error("Command is required. Check help.")
        sys.exit(1)

    # Dispatch to the right function as per func attribute
    if not hasattr(sys.modules[__name__], parsed_args.func):
        LOG.error('Command not implemented.')
        sys.exit(1)
    getattr(sys.modules[__name__], parsed_args.func)(parsed_args)
