#!/usr/bin/python3


import gi
import git
import os
import sys
import logging
from logging import error, info

from gi.repository import GLib

gi.require_version("Modulemd", "2.0")
from gi.repository import Modulemd  # noqa

logging.getLogger().setLevel(logging.INFO)


def do_validate(filename):
    # Valid filenames must end in ".yaml" to be properly included by Pungi
    if not filename.endswith(".yaml"):
        error(
            "{} does not end with .yaml. It will not be included by "
            "Pungi. If this file does not contain defaults, it should "
            "be added to the tests/exclusions.txt file.".format(filename)
        )
        return False, None

    # The files must parse correctly
    idx = Modulemd.ModuleIndex()
    try:
        (objects, failures) = idx.update_from_file(filename, True)
    except GLib.Error as e:
        error("{}".format(e.message))
        return False, None

    if failures:
        for failure in failures:
            error(
                "Failed subdocument ({}): \n{}\n".format(
                    failure.get_gerror().message, failure.get_yaml()
                )
            )
        return False, None

    # There must be exactly one object per file
    names = idx.get_module_names()
    if len(names) != 1:
        error("There must be exactly one module represented by this file")
        return False, None

    # The files must have exactly one Modulemd.Defaults object
    module = idx.get_module(names[0])
    defaults = module.get_defaults()

    if defaults is None:
        error(
            "No defaults document provided for {}".format(
                module.props.module_name
            )
        )
        return False, None

    # The files must not contain any streams
    if len(module.get_stream_names()) != 0:
        error("File included a stream document.")
        return False, None

    # Filenames must match their contents
    expected_name = os.path.basename(filename).rsplit(".", maxsplit=1)[0]

    if expected_name != defaults.props.module_name:
        error(
            'Module name "{}" doesn\'t match filename "{}.yaml"'.format(
                defaults.props.module_name, expected_name
            )
        )
        return False, None

    default_stream = defaults.get_default_stream()
    if default_stream:
        # Default streams must also appear in the profiles list
        if defaults.get_default_profiles_for_stream(default_stream) is None:
            error(
                "Stream '{}' is missing from the profiles for '{}'".format(
                    default_stream, defaults.get_module_name()
                )
            )
            return False, None

    # Modules in Fedora must not specify "Intents"
    # TODO: This needs a new interface exposed in libmodulemd v2

    info("{} is valid".format(filename))
    return (True, idx)


def main():
    result = os.EX_OK

    if sys.argv[1]:
        script_dir = os.path.abspath(sys.argv[1])
    else:
        script_dir = os.path.dirname(os.path.realpath(__file__))
    defaults_dir = os.path.abspath(os.path.join(script_dir, '..'))
    overrides_dir = os.path.join(defaults_dir, 'overrides')

    # Get the repo we're running in
    repo = git.Repo(defaults_dir, search_parent_directories=True)

    # Get the list of files in this repository
    files = [x for (x, y) in repo.index.entries.keys()]

    # Get the list of excluded files
    exclusions = []
    with open(os.path.join(script_dir, "exclusions.txt")) as f:
        for line in f:
            stripped_line = line.strip()
            if not stripped_line or stripped_line.startswith("#"):
                # Ignore comments and empty lines
                continue
            exclusions.append(line.strip())

    # Validate all of the files
    for file in files:
        excluded = False
        for excl in exclusions:
            if file.startswith(excl):
                excluded = True
                break
        if not excluded:
            (valid, idx) = do_validate(file)
            if not valid:
                error("{} failed to validate".format(file))
                result = os.EX_DATAERR

    if result == os.EX_DATAERR:
        return result

    # For sanity's sake, also do a merge of all the defaults to make sure no
    # conflicts arise that weren't detected by the above tests. This should be
    # impossible.
    try:
        idx = Modulemd.ModuleIndex()
        idx.update_from_defaults_directory(path=defaults_dir,
                                           strict=True)
    except GLib.Error as e:
        error("Could not merge all defaults: {}".format(e.message))
        result = os.EX_DATAERR

    if result == os.EX_OK:
        info("Merging all of the documents encountered no errors.")

    print("\nDefault streams (Runtime):")
    print("================")
    for m, s in idx.get_default_streams().items():
        print("{}:{}".format(m, s))

    try:
        idx = Modulemd.ModuleIndex()
        idx.update_from_defaults_directory(path=defaults_dir,
                                           overrides_path=overrides_dir,
                                           strict=True)
    except GLib.Error as e:
        error("Could not merge all defaults: {}".format(e.message))
        result = os.EX_DATAERR

    if result == os.EX_OK:
        info("Merging all of the documents encountered no errors.")

    print("\nDefault streams (Buildroot):")
    print("================")
    for m, s in idx.get_default_streams().items():
        print("{}:{}".format(m, s))

    return result


if __name__ == "__main__":
    sys.exit(main())