#! /usr/bin/python3 -sP

import copy
import json
import os
import sys

try:
    import configparser
except ImportError:
    import ConfigParser as configparser
import pprint

try:
    import xmlrpclib
except ImportError:
    import xmlrpc.client as xmlrpclib
import base64
import bz2

# globals
exclude_list = []


class HostConfig:
    """Holder for config info from the configuration file"""

    def __init__(self):
        self.config = {
            "version": 0,
            "global": {},
            "site": {},
            "host": {},
            "stats": {},
        }


def get_exclude_dir_patterns_from_file(options):
    global exclude_list
    if options.exclude_from:
        f = open(options.exclude_from)
        for line in f:
            line = line.strip()
            if line.startswith("#"):
                continue
            exclude_list.append(line)
        f.close()


def exclude_dirs(root, dirs):
    global exclude_list
    copydirs = copy.copy(dirs)
    # iterate over the copy so we can modify the original
    for d in copydirs:
        # keep trailing /, we know it's a directory
        testdir = os.path.join(root, d, "")
        for pattern in exclude_list:
            if pattern in testdir:
                dirs.remove(d)
                break


def gen_dirtree(path):
    # structure here is:
    # dirtree is a dict
    # {
    #   dirpath :
    #                {
    #                   filename1 : size1,
    #                   filename2 : size2,
    #                   ...
    #                 },
    #   ...
    # }
    #
    # 2009-03-09: MM's web app ignores the statfiles dict.  So don't bother
    # generating it.

    dirtree = {}
    for dirpath, dirnames, _filenames in os.walk(path):
        exclude_dirs(dirpath, dirnames)
        statfiles = {}
        if path.endswith("/"):
            short_path = dirpath[len(path) :]
        else:
            short_path = dirpath[len(path) + 1 :]
        if len(short_path) > 0:
            dirtree[short_path] = statfiles
        else:
            dirtree[""] = statfiles

    return dirtree


def errorprint(error):
    sys.stderr.write(error + "\n")


class MissingOption(Exception):
    pass


def check_required_options(conf, section, required_options):
    for o in required_options:
        if not conf.has_option(section, o):
            errorprint(f"missing required option {o} in config [{section}]")
            raise MissingOption()
    return True


def parse_value(value):
    """Split multi-line values into a list"""
    if value.find("\n") > -1:
        return value.split()
    return value


def parse_section(conf, section, item, required_options, optional_options=None):
    if optional_options is None:
        optional_options = []
    if conf.has_option(section, "enabled"):
        if conf.get(section, "enabled") != "1" and section.lower() in item.config:
            print("removing disabled section %s" % (section))
            del item.config[section.lower()]
            return False

    if not check_required_options(conf, section, required_options):
        return False

    if section.lower() not in item.config:
        item.config[section.lower()] = {}

    for o in required_options:
        item.config[section.lower()][o] = parse_value(conf.get(section, o))
    for o in optional_options:
        if conf.has_option(section, o):
            item.config[section.lower()][o] = parse_value(conf.get(section, o))

    return True


def parse_global(conf, section, item):
    required_options = ["enabled", "server"]
    if not parse_section(conf, section, item, required_options):
        errorprint("missing required options (server AND enabled) in [%s] section" % (section))
        return False
    return True


def parse_site(conf, section, item):
    required_options = ["enabled", "name", "password"]
    return parse_section(conf, section, item, required_options)


def parse_host(conf, section, item):
    required_options = ["enabled", "name"]
    optional_options = ["user_active"]
    return parse_section(conf, section, item, required_options, optional_options=optional_options)


def get_stats(conf, section):
    if conf.has_option(section, "enabled"):
        if conf.get(section, "enabled") != "1":
            return None
    statsdata = {}
    for name, _value in conf.items(section):
        if name == "enabled":
            continue
        filenames = parse_value(conf.get(section, name))
        if not isinstance(filenames, list):
            filenames = [filenames]
        contents = []
        for fn in filenames:
            try:
                f = open(fn)
                contents = contents + f.readlines()
                statsdata[name] = json.dumps(contents)
                f.close()
            except OSError:
                pass
    return statsdata


def parse_category(conf, section, item, crawl):
    required_options = ["enabled", "path"]
    if not parse_section(conf, section, item, required_options):
        return False

    if crawl:
        dirtree = gen_dirtree(conf.get(section, "path"))
        item.config[section.lower()]["dirtree"] = dirtree
    # database doesn't need to know the disk path
    del item.config[section.lower()]["path"]


def config(cfg, item, crawl=True):
    conf = configparser.ConfigParser()
    files = conf.read(cfg)
    if files == []:
        errorprint("Configuration file %s not found" % (cfg))
        return False
    conf.read(cfg)

    try:
        # don't grab parse_stats here
        for section, parsefunc in [
            ("global", parse_global),
            ("site", parse_site),
            ("host", parse_host),
        ]:
            if conf.has_section(section):
                if not parsefunc(conf, section, item):
                    return False
            else:
                errorprint("Invalid configuration - missing section [%s]" % (section))
                sys.exit(1)

        for section in conf.sections():
            if section in ["global", "site", "host", "stats"]:
                continue
            parse_category(conf, section, item, crawl)

    except MissingOption:
        errorprint("Invalid configuration - Exiting")
        sys.exit(1)

    return True


options = None


def main():
    from optparse import OptionParser

    parser = OptionParser(usage=sys.argv[0] + " [options]")
    parser.add_option(
        "-c",
        "--config",
        dest="config",
        default="/etc/mirrormanager-client/report_mirror.conf",
        help="Configuration filename (required)",
    )
    #    parser.add_option("-s", "--stats",
    #                      action="store_true",
    #                      dest="stats",
    #                      default=False,
    #                      help='Send stats')
    parser.add_option(
        "-i", "--input", dest="input", default=None, help="Input filename (for debugging)"
    )
    parser.add_option(
        "-o", "--output", dest="output", default=None, help="Output filename (for debugging)"
    )
    parser.add_option(
        "-n",
        "--no-send",
        action="store_true",
        dest="no_send",
        default=False,
        help="Don't send data to the server.",
    )
    parser.add_option(
        "-d",
        "--debug",
        action="store_true",
        dest="debug",
        default=False,
        help="Enable debugging output",
    )
    parser.add_option(
        "--exclude-from", dest="exclude_from", default=None, help="get exclude patterns from FILE"
    )

    (options, args) = parser.parse_args()
    item = HostConfig()
    get_exclude_dir_patterns_from_file(options)
    if options.input:
        infile = open(options.input, "rb")
        item.config = json.load(infile)
        infile.close()
        if not config(options.config, item, crawl=False):
            sys.exit(1)
    else:
        if not config(options.config, item, crawl=True):
            sys.exit(1)

    p = json.dumps(item.config)

    if options.debug:
        pp = pprint.PrettyPrinter(indent=4)
        pp.pprint(item.config)

    if options.output is not None:
        outfile = open(options.output, "wb")
        outfile.write(p)
        outfile.close()

    #    if options.stats:
    #        statdata = get_stats(conf, 'stats')

    # upload p and statsdata here
    if item.config.get("global", {}).get("enabled") != "1":
        sys.exit(1)

    if not options.no_send:
        #  print("Connecting to %s" % item.config['global']['server'])
        server = xmlrpclib.ServerProxy(item.config["global"]["server"])

        data = base64.urlsafe_b64encode(bz2.compress(p.encode())).decode()

        if data is not None:
            try:
                print(server.checkin(data))
            except OSError as m:
                print("Error checking in: %s.  Please try again later." % (m[1]))
            except xmlrpclib.ProtocolError:
                print(
                    "Error checking in: Service Temporarily "
                    "Unavailable.  Please try again later."
                )
                sys.exit(1)
            except xmlrpclib.Fault:
                print(
                    "Error checking in.  Connection closed before "
                    "checkin complete.  Please try again later."
                )
                sys.exit(1)


if __name__ == "__main__":
    main()
