# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

from mach.decorators import Command, CommandArgument

TODO_FILL_DESCRIPTION = "TODO: Fill in this description. You _can_ use **markdown**."
GENERATED_BY_DESCRIPTION = (
    "This {metric} was generated to correspond to the Legacy Telemetry {kind} {name}."
)
DESCRIPTION_INDENT = "      "
LIST_INDENT = "      - "
BUG_URL_TEMPLATE = "https://bugzil.la/{}"

GLEAN_METRIC_TEMPLATE = """
  {name}:
    type: {metric_type}
    description: >
{multiline_description}
    bugs:{bugs_alias}{bugs_list}
    data_reviews:{data_alias}{bugs_list}
    notification_emails:{emails_alias}{emails_list}
    expires: {expiry}
    {extra}telemetry_mirror: {legacy_enum}
""".strip(
    "\n"
)

EXTRA_KEY_DESCRIPTION_INDENT = DESCRIPTION_INDENT + "    "
VALUE_EXTRA_DESCRIPTION = "The `value` of the event. Mirrors to the Legacy Telemetry event's `value` parameter."
EXTRA_KEY_TEMPLATE = """
      {name}:
        description: >
{description}
        type: string
""".strip(
    "\n"
)


@Command(
    "gifft",
    category="misc",
    description="Generate a Glean metric definition for a given Legacy Telemetry probe. Only supports Events and Scalars.",
)
@CommandArgument(
    "telemetry_probe_name", help="Telemetry probe name (e.g. readermode.view)"
)
def mach_gifft(command_context, telemetry_probe_name):
    from os import path

    telemetrydir = path.join(
        command_context.topsrcdir, "toolkit", "components", "telemetry"
    )

    import re

    def to_snake_case(camel):
        return re.sub("([A-Z]+)", r"_\1", camel).lower().replace("__", "_").strip("_")

    import itertools
    import sys

    sys.path.append(path.join(telemetrydir, "build_scripts"))
    import textwrap

    from mozparsers import parse_events, parse_scalars

    events = parse_events.load_events(path.join(telemetrydir, "Events.yaml"), True)
    for e in events:
        if e.category + "." + e.name == telemetry_probe_name:
            # There are four levels of identification in Legacy Telemetry event
            # definitions: category, name/family, method, and object.
            # If not present, the `method` property will supply the name/family.
            # GIFFT will mirror to a specific identified C++ enum, which means
            # we need to generate Glean events for every combination of method
            # and object.
            category = e.category
            emails_alias = bugs_alias = data_alias = extra_alias = ""
            print(f"{to_snake_case(category)}:")
            for m, o in itertools.product(e.methods, e.objects):
                legacy_name = category + "." + m + "#" + o
                name = m + "_" + o
                description = e._definition.get("description", TODO_FILL_DESCRIPTION)
                multiline_description = textwrap.fill(
                    description,
                    width=80 - len(DESCRIPTION_INDENT),
                    initial_indent=DESCRIPTION_INDENT,
                    subsequent_indent=DESCRIPTION_INDENT,
                )
                multiline_description += "\n"
                multiline_description += textwrap.fill(
                    GENERATED_BY_DESCRIPTION.format(
                        metric="event", kind="event", name=legacy_name
                    ),
                    width=80 - len(DESCRIPTION_INDENT),
                    initial_indent=DESCRIPTION_INDENT,
                    subsequent_indent=DESCRIPTION_INDENT,
                )

                alias_prefix = category.replace(".", "_") + f"_{m}_"
                if bugs_alias:
                    bugs_list = ""
                else:
                    bugs_alias = f"{alias_prefix}bugs"
                    data_alias = f"{alias_prefix}data_reviews"
                    bugs_list = "\n" + textwrap.indent(
                        "\n".join(
                            map(
                                lambda b: BUG_URL_TEMPLATE.format(b),
                                e._definition.get("bug_numbers", []),
                            )
                        ),
                        LIST_INDENT,
                    )
                if emails_alias:
                    emails_list = ""
                else:
                    emails_alias = f"{alias_prefix}emails"
                    emails_list = "\n" + textwrap.indent(
                        "\n".join(e._definition.get("notification_emails", [])),
                        LIST_INDENT,
                    )

                # expiry_version is a string like `"123.0a1"` or `"never"`,
                # but Glean wants a number like `123` or `never`.
                expiry = e.expiry_version.strip('"').split(".")[0]

                if extra_alias:
                    extra_keys = ""
                else:
                    extra_alias = f"{alias_prefix}extra"
                    multiline_extra_description = textwrap.fill(
                        VALUE_EXTRA_DESCRIPTION,
                        width=80 - len(EXTRA_KEY_DESCRIPTION_INDENT),
                        initial_indent=EXTRA_KEY_DESCRIPTION_INDENT,
                        subsequent_indent=EXTRA_KEY_DESCRIPTION_INDENT,
                    )
                    extra_keys = "\n" + EXTRA_KEY_TEMPLATE.format(
                        name="value", description=multiline_extra_description
                    )
                    for key_name, key_description in e._definition.get(
                        "extra_keys", {}
                    ).items():
                        extra_keys += "\n"
                        extra_keys += EXTRA_KEY_TEMPLATE.format(
                            name=key_name,
                            description=textwrap.indent(
                                key_description, EXTRA_KEY_DESCRIPTION_INDENT
                            ),
                        )

                legacy_enum = (
                    parse_events.convert_to_cpp_identifier(category, ".") + "_"
                )
                legacy_enum += parse_events.convert_to_cpp_identifier(m, "_") + "_"
                legacy_enum += parse_events.convert_to_cpp_identifier(o, "_")

                def generate_alias(list, alias):
                    if len(e.methods) == 1 and len(e.objects) == 1:
                        return ""
                    if list:
                        return f" &{alias}"
                    else:
                        return f" *{alias}"

                print(
                    GLEAN_METRIC_TEMPLATE.format(
                        name=to_snake_case(name),
                        metric_type="event",
                        multiline_description=multiline_description,
                        bugs_alias=generate_alias(bugs_list, bugs_alias),
                        bugs_list=bugs_list,
                        data_alias=generate_alias(bugs_list, data_alias),
                        emails_alias=generate_alias(emails_list, emails_alias),
                        emails_list=emails_list,
                        expiry=expiry,
                        extra=f"extra_keys:{generate_alias(extra_keys, extra_alias)}{extra_keys}\n    ",
                        legacy_enum=legacy_enum,
                    )
                )
                print()  # We want a newline between event definitions.
            break

    scalars = parse_scalars.load_scalars(path.join(telemetrydir, "Scalars.yaml"))
    for s in scalars:
        if s.category + "." + s.name == telemetry_probe_name:
            category = s.category
            name = s.name
            legacy_name = category + "." + name
            description = s._definition.get("description", TODO_FILL_DESCRIPTION)
            multiline_description = textwrap.fill(
                description,
                width=80 - len(DESCRIPTION_INDENT),
                initial_indent=DESCRIPTION_INDENT,
                subsequent_indent=DESCRIPTION_INDENT,
            )
            multiline_description += "\n"
            multiline_description += textwrap.fill(
                GENERATED_BY_DESCRIPTION.format(
                    name=legacy_name, metric="metric", kind="scalar"
                ),
                width=80 - len(DESCRIPTION_INDENT),
                initial_indent=DESCRIPTION_INDENT,
                subsequent_indent=DESCRIPTION_INDENT,
            )

            bugs_list = "\n" + textwrap.indent(
                "\n".join(
                    map(
                        lambda b: BUG_URL_TEMPLATE.format(b),
                        s._definition.get("bug_numbers", []),
                    )
                ),
                LIST_INDENT,
            )
            emails_list = "\n" + textwrap.indent(
                "\n".join(s._definition.get("notification_emails", [])),
                LIST_INDENT,
            )

            # expires is a string like `"123.0a1"` or `"never"`,
            # but Glean wants a number like `123` or `never`.
            expiry = s.expires.strip('"').split(".")[0]

            metric_type = s.kind
            if metric_type == "uint":
                if s.keyed:
                    metric_type = "labeled_counter"
                else:
                    # The caller will replace "counter" with "quantity" in the output when needed.
                    metric_type = "counter"
            elif s.keyed:
                if metric_type == "boolean":
                    metric_type = "labeled_boolean"
                elif metric_type == "string":
                    metric_type = "labeled_string"

            extra = ""
            if s.keys:
                extra = (
                    "labels:\n"
                    + "\n".join(map(lambda key: "      - " + key, s.keys))
                    + "\n    "
                )

            print(f"{to_snake_case(category)}:")
            print(
                GLEAN_METRIC_TEMPLATE.format(
                    name=to_snake_case(name),
                    metric_type=metric_type,
                    multiline_description=multiline_description,
                    bugs_alias="",
                    bugs_list=bugs_list,
                    data_alias="",
                    emails_alias="",
                    emails_list=emails_list,
                    extra=extra,
                    expiry=expiry,
                    legacy_enum=s.enum_label,
                )
            )
            print()  # We want a newline between metric definitions.
