# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License.
# This product includes software developed at Datadog (https://www.datadoghq.com/).
# Copyright 2015-Present Datadog, Inc
# stdlib
import json
import os.path
import platform
import sys
import webbrowser

# 3p
import argparse

# datadog
from datadog import api
from datadog.dogshell.common import report_errors, report_warnings, print_err
from datadog.util.format import pretty_json
from datetime import datetime


class TimeboardClient(object):
    @classmethod
    def setup_parser(cls, subparsers):
        parser = subparsers.add_parser("timeboard", help="Create, edit, and delete timeboards")
        parser.add_argument(
            "--string_ids",
            action="store_true",
            dest="string_ids",
            help="Represent timeboard IDs as strings instead of ints in JSON",
        )

        verb_parsers = parser.add_subparsers(title="Verbs", dest="verb")
        verb_parsers.required = True

        post_parser = verb_parsers.add_parser("post", help="Create timeboards")
        post_parser.add_argument("title", help="title for the new timeboard")
        post_parser.add_argument("description", help="short description of the timeboard")
        post_parser.add_argument(
            "graphs", help="graph definitions as a JSON string. if unset," " reads from stdin.", nargs="?"
        )
        post_parser.add_argument(
            "--template_variables",
            type=_template_variables,
            default=[],
            help="a json list of template variable dicts, e.g. "
            "[{'name': 'host', 'prefix': 'host', "
            "'default': 'host:my-host'}]'",
        )

        post_parser.set_defaults(func=cls._post)

        update_parser = verb_parsers.add_parser("update", help="Update existing timeboards")
        update_parser.add_argument("timeboard_id", help="timeboard to replace" " with the new definition")
        update_parser.add_argument("title", help="new title for the timeboard")
        update_parser.add_argument("description", help="short description of the timeboard")
        update_parser.add_argument(
            "graphs", help="graph definitions as a JSON string." " if unset, reads from stdin", nargs="?"
        )
        update_parser.add_argument(
            "--template_variables",
            type=_template_variables,
            default=[],
            help="a json list of template variable dicts, e.g. "
            "[{'name': 'host', 'prefix': 'host', "
            "'default': 'host:my-host'}]'",
        )
        update_parser.set_defaults(func=cls._update)

        show_parser = verb_parsers.add_parser("show", help="Show a timeboard definition")
        show_parser.add_argument("timeboard_id", help="timeboard to show")
        show_parser.set_defaults(func=cls._show)

        show_all_parser = verb_parsers.add_parser("show_all", help="Show a list of all timeboards")
        show_all_parser.set_defaults(func=cls._show_all)

        pull_parser = verb_parsers.add_parser("pull", help="Pull a timeboard on the server" " into a local file")
        pull_parser.add_argument("timeboard_id", help="ID of timeboard to pull")
        pull_parser.add_argument("filename", help="file to pull timeboard into")
        pull_parser.set_defaults(func=cls._pull)

        pull_all_parser = verb_parsers.add_parser("pull_all", help="Pull all timeboards" " into files in a directory")
        pull_all_parser.add_argument("pull_dir", help="directory to pull timeboards into")
        pull_all_parser.set_defaults(func=cls._pull_all)

        push_parser = verb_parsers.add_parser(
            "push", help="Push updates to timeboards" " from local files to the server"
        )
        push_parser.add_argument(
            "--append_auto_text",
            action="store_true",
            dest="append_auto_text",
            help="When pushing to the server, appends filename"
            " and timestamp to the end of the timeboard description",
        )
        push_parser.add_argument(
            "file", help="timeboard files to push to the server", nargs="+", type=argparse.FileType("r")
        )
        push_parser.set_defaults(func=cls._push)

        new_file_parser = verb_parsers.add_parser(
            "new_file", help="Create a new timeboard" " and put its contents in a file"
        )
        new_file_parser.add_argument("filename", help="name of file to create with empty timeboard")
        new_file_parser.add_argument(
            "graphs", help="graph definitions as a JSON string." " if unset, reads from stdin.", nargs="?"
        )
        new_file_parser.set_defaults(func=cls._new_file)

        web_view_parser = verb_parsers.add_parser("web_view", help="View the timeboard in a web browser")
        web_view_parser.add_argument("file", help="timeboard file", type=argparse.FileType("r"))
        web_view_parser.set_defaults(func=cls._web_view)

        delete_parser = verb_parsers.add_parser("delete", help="Delete timeboards")
        delete_parser.add_argument("timeboard_id", help="timeboard to delete")
        delete_parser.set_defaults(func=cls._delete)

    @classmethod
    def _pull(cls, args):
        cls._write_dash_to_file(args.timeboard_id, args.filename, args.timeout, args.format, args.string_ids)

    @classmethod
    def _pull_all(cls, args):
        api._timeout = args.timeout

        def _title_to_filename(title):
            # Get a lowercased version with most punctuation stripped out...
            no_punct = "".join([c for c in title.lower() if c.isalnum() or c in [" ", "_", "-"]])
            # Now replace all -'s, _'s and spaces with "_", and strip trailing _
            return no_punct.replace(" ", "_").replace("-", "_").strip("_")

        format = args.format
        res = api.Timeboard.get_all()
        report_warnings(res)
        report_errors(res)

        if not os.path.exists(args.pull_dir):
            os.mkdir(args.pull_dir, 0o755)

        used_filenames = set()
        for dash_summary in res["dashes"]:
            filename = _title_to_filename(dash_summary["title"])
            if filename in used_filenames:
                filename = filename + "-" + dash_summary["id"]
            used_filenames.add(filename)

            cls._write_dash_to_file(
                dash_summary["id"],
                os.path.join(args.pull_dir, filename + ".json"),
                args.timeout,
                format,
                args.string_ids,
            )
        if format == "pretty":
            print(
                ("\n### Total: {0} dashboards to {1} ###".format(len(used_filenames), os.path.realpath(args.pull_dir)))
            )

    @classmethod
    def _new_file(cls, args):
        api._timeout = args.timeout
        format = args.format
        graphs = args.graphs
        if args.graphs is None:
            graphs = sys.stdin.read()
        graphs = json.loads(graphs)
        res = api.Timeboard.create(
            title=args.filename, description="Description for {0}".format(args.filename), graphs=[graphs]
        )

        report_warnings(res)
        report_errors(res)

        cls._write_dash_to_file(res["dash"]["id"], args.filename, args.timeout, format, args.string_ids)

        if format == "pretty":
            print(pretty_json(res))
        else:
            print(json.dumps(res))

    @classmethod
    def _write_dash_to_file(cls, dash_id, filename, timeout, format="raw", string_ids=False):
        with open(filename, "w") as f:
            res = api.Timeboard.get(dash_id)
            report_warnings(res)
            report_errors(res)

            dash_obj = res["dash"]
            if "resource" in dash_obj:
                del dash_obj["resource"]
            if "url" in dash_obj:
                del dash_obj["url"]

            if string_ids:
                dash_obj["id"] = str(dash_obj["id"])

            if not dash_obj.get("template_variables"):
                dash_obj.pop("template_variables", None)

            json.dump(dash_obj, f, indent=2)

            if format == "pretty":
                print(u"Downloaded dashboard {0} to file {1}".format(dash_id, filename))
            else:
                print(u"{0} {1}".format(dash_id, filename))

    @classmethod
    def _push(cls, args):
        api._timeout = args.timeout
        for f in args.file:
            try:
                dash_obj = json.load(f)
            except Exception as err:
                raise Exception("Could not parse {0}: {1}".format(f.name, err))

            if args.append_auto_text:
                datetime_str = datetime.now().strftime("%x %X")
                auto_text = "<br/>\nUpdated at {0} from {1} ({2}) on {3}".format(
                    datetime_str, f.name, dash_obj["id"], platform.node()
                )
                dash_obj["description"] += auto_text
            tpl_vars = dash_obj.get("template_variables", [])

            if "id" in dash_obj:
                # Always convert to int, in case it was originally a string.
                dash_obj["id"] = int(dash_obj["id"])
                res = api.Timeboard.update(
                    dash_obj["id"],
                    title=dash_obj["title"],
                    description=dash_obj["description"],
                    graphs=dash_obj["graphs"],
                    template_variables=tpl_vars,
                )
            else:
                res = api.Timeboard.create(
                    title=dash_obj["title"],
                    description=dash_obj["description"],
                    graphs=dash_obj["graphs"],
                    template_variables=tpl_vars,
                )

            if "errors" in res:
                print_err("Upload of dashboard {0} from file {1} failed.".format(dash_obj["id"], f.name))

            report_warnings(res)
            report_errors(res)

            if format == "pretty":
                print(pretty_json(res))
            else:
                print(json.dumps(res))

            if args.format == "pretty":
                print("Uploaded file {0} (dashboard {1})".format(f.name, dash_obj["id"]))

    @classmethod
    def _post(cls, args):
        api._timeout = args.timeout
        format = args.format
        graphs = args.graphs
        if args.graphs is None:
            graphs = sys.stdin.read()
        graphs = json.loads(graphs)
        res = api.Timeboard.create(
            title=args.title, description=args.description, graphs=[graphs], template_variables=args.template_variables
        )
        report_warnings(res)
        report_errors(res)
        if format == "pretty":
            print(pretty_json(res))
        else:
            print(json.dumps(res))

    @classmethod
    def _update(cls, args):
        api._timeout = args.timeout
        format = args.format
        graphs = args.graphs
        if args.graphs is None:
            graphs = sys.stdin.read()
        graphs = json.loads(graphs)

        res = api.Timeboard.update(
            args.timeboard_id,
            title=args.title,
            description=args.description,
            graphs=graphs,
            template_variables=args.template_variables,
        )
        report_warnings(res)
        report_errors(res)
        if format == "pretty":
            print(pretty_json(res))
        else:
            print(json.dumps(res))

    @classmethod
    def _show(cls, args):
        api._timeout = args.timeout
        format = args.format
        res = api.Timeboard.get(args.timeboard_id)
        report_warnings(res)
        report_errors(res)

        if args.string_ids:
            res["dash"]["id"] = str(res["dash"]["id"])

        if format == "pretty":
            print(pretty_json(res))
        else:
            print(json.dumps(res))

    @classmethod
    def _show_all(cls, args):
        api._timeout = args.timeout
        format = args.format
        res = api.Timeboard.get_all()
        report_warnings(res)
        report_errors(res)

        if args.string_ids:
            for d in res["dashes"]:
                d["id"] = str(d["id"])

        if format == "pretty":
            print(pretty_json(res))
        elif format == "raw":
            print(json.dumps(res))
        else:
            for d in res["dashes"]:
                print("\t".join([(d["id"]), (d["resource"]), (d["title"]), cls._escape(d["description"])]))

    @classmethod
    def _delete(cls, args):
        api._timeout = args.timeout
        res = api.Timeboard.delete(args.timeboard_id)
        if res is not None:
            report_warnings(res)
            report_errors(res)

    @classmethod
    def _web_view(cls, args):
        dash_id = json.load(args.file)["id"]
        url = api._api_host + "/dash/dash/{0}".format(dash_id)
        webbrowser.open(url)

    @classmethod
    def _escape(cls, s):
        return s.replace("\r", "\\r").replace("\n", "\\n").replace("\t", "\\t") if s else ""


def _template_variables(tpl_var_input):
    if "[" not in tpl_var_input:
        return [v.strip() for v in tpl_var_input.split(",")]
    else:
        try:
            return json.loads(tpl_var_input)
        except Exception:
            raise argparse.ArgumentTypeError("bad template_variable json parameter")
