"""Generate JSON Grafana dashboards."""

import argparse
import json
import os
import sys


DASHBOARD_SUFFIX = '.dashboard.py'


class DashboardError(Exception):
    """Raised when there is something wrong with a dashboard."""


def load_dashboard(path):
    """Load a ``Dashboard`` from a Python definition.

    :param str path: Path to a *.dashboard.py file that defines a variable,
        ``dashboard``.
    :return: A ``Dashboard``
    """
    if sys.version_info[0] == 3 and sys.version_info[1] >= 5:
        import importlib.util
        spec = importlib.util.spec_from_file_location('dashboard', path)
        module = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(module)
    elif sys.version_info[0] == 3 and (sys.version_info[1] >= 3 or sys.version_info[1] <= 4):
        from importlib.machinery import SourceFileLoader
        module = SourceFileLoader("dashboard", path).load_module()
    elif sys.version_info[0] == 2:
        import imp
        module = imp.load_source('dashboard', path)
    else:
        import importlib
        module = importlib.load_source('dashboard', path)

    marker = object()
    dashboard = getattr(module, 'dashboard', marker)
    if dashboard is marker:
        raise DashboardError(
            "Dashboard definition {} does not define 'dashboard'".format(path))
    return dashboard


class DashboardEncoder(json.JSONEncoder):
    """Encode dashboard objects."""

    def default(self, obj):
        to_json_data = getattr(obj, 'to_json_data', None)
        if to_json_data:
            return to_json_data()
        return json.JSONEncoder.default(self, obj)


def write_dashboard(dashboard, stream):
    json.dump(
        dashboard.to_json_data(), stream, sort_keys=True, indent=2,
        cls=DashboardEncoder)
    stream.write('\n')


def print_dashboard(dashboard):
    write_dashboard(dashboard, stream=sys.stdout)


def write_dashboards(paths):
    for path in paths:
        dashboard = load_dashboard(path)
        with open(get_json_path(path), 'w') as json_file:
            write_dashboard(dashboard, json_file)


def get_json_path(path):
    assert path.endswith(DASHBOARD_SUFFIX)
    return '{}.json'.format(path[:-len(DASHBOARD_SUFFIX)])


def dashboard_path(path):
    abspath = os.path.abspath(path)
    if not abspath.endswith(DASHBOARD_SUFFIX):
        raise argparse.ArgumentTypeError(
            'Dashboard file {} does not end with {}'.format(
                path, DASHBOARD_SUFFIX))
    return abspath


def generate_dashboards(args):
    """Script for generating multiple dashboards at a time."""
    parser = argparse.ArgumentParser(prog='generate-dashboards')
    parser.add_argument(
        'dashboards', metavar='DASHBOARD', type=os.path.abspath,
        nargs='+', help='Path to dashboard definition',
    )
    opts = parser.parse_args(args)
    try:
        write_dashboards(opts.dashboards)
    except DashboardError as e:
        sys.stderr.write('ERROR: {}\n'.format(e))
        return 1
    return 0


def generate_dashboard(args):
    parser = argparse.ArgumentParser(prog='generate-dashboard')
    parser.add_argument(
        '--output', '-o', type=os.path.abspath,
        help='Where to write the dashboard JSON'
    )
    parser.add_argument(
        'dashboard', metavar='DASHBOARD', type=os.path.abspath,
        help='Path to dashboard definition',
    )
    opts = parser.parse_args(args)
    try:
        dashboard = load_dashboard(opts.dashboard)
        if not opts.output:
            print_dashboard(dashboard)
        else:
            with open(opts.output, 'w') as output:
                write_dashboard(dashboard, output)
    except DashboardError as e:
        sys.stderr.write('ERROR: {}\n'.format(e))
        return 1
    return 0


def run_script(f):
    sys.exit(f(sys.argv[1:]))


def generate_dashboards_script():
    """Entry point for generate-dashboards."""
    run_script(generate_dashboards)


def generate_dashboard_script():
    """Entry point for generate-dashboard."""
    run_script(generate_dashboard)
