# 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/.

import argparse
import hashlib
import re
from datetime import datetime
from urllib.parse import urlparse
from xml.etree import ElementTree

import requests


def check_hash(plugin, url, valid_urls):
    if url in valid_urls:
        return
    valid_urls[url] = True

    response = requests.get(url)
    response.raise_for_status()

    if "hashValue" in plugin.attrib:
        hashValue = hashlib.sha512(response.content).hexdigest()
        if hashValue != plugin.attrib["hashValue"]:
            raise Exception(
                "Given hash {} and calculated hash {} differ",
                plugin.attrib["hashValue"],
                hashValue,
            )
    if "size" in plugin.attrib:
        size = len(response.content)
        if size != int(plugin.attrib["size"]):
            raise Exception(
                "Given size {} and calculated size {} differ",
                int(plugin.attrib["size"]),
                size,
            )


def fetch_balrog_xml(
    url_base: str, plugin_id, version: str, buildid: str, channels, targets, checkHash
) -> str:
    url = "{url_base}/{version}/{buildid}/{target}/en-US/{channel}/default/default/default/update.xml"
    valid_urls = {}
    results = {}
    for channel in channels:
        results[channel] = {}
        for target in targets:
            balrog_url = url.format_map(
                {
                    "url_base": url_base,
                    "buildid": buildid,
                    "channel": channel,
                    "version": version,
                    "target": target,
                }
            )

            response = requests.get(balrog_url)
            response.raise_for_status()

            plugin_urls = []
            tree = ElementTree.fromstring(response.content)
            for plugin in tree.findall("./addons/addon"):
                if not "id" in plugin.attrib:
                    continue
                if plugin.attrib["id"] != plugin_id:
                    continue
                if "URL" in plugin.attrib:
                    if checkHash:
                        check_hash(plugin, plugin.attrib["URL"], valid_urls)
                    plugin_urls.append(plugin.attrib["URL"])
                for mirror in plugin.findall("./mirror"):
                    if "URL" in mirror.attrib:
                        if checkHash:
                            check_hash(plugin, plugin.attrib["URL"], valid_urls)
                        plugin_urls.append(mirror.attrib["URL"])

            results[channel][target] = plugin_urls

    matching_channels = {}
    for channel in channels:
        matching_channels[channel] = [channel]
        for other_channel in channels:
            if (
                channel == other_channel
                or channel not in results
                or other_channel not in results
            ):
                continue
            if results[channel] == results[other_channel]:
                matching_channels[channel].append(other_channel)
                del results[other_channel]

    for channel in results:
        print(", ".join(matching_channels[channel]))
        for target in targets:
            print(f"\t{target}")
            for url in results[channel][target]:
                print(f"\t\t{url}")


def main():
    examples = """examples:
  python dom/media/tools/checkGmpBalrog.py widevine 133.0
  python dom/media/tools/checkGmpBalrog.py widevine 133.0 --target Darwin_aarch64-gcc3 Darwin_x86_64-gcc3
  python dom/media/tools/checkGmpBalrog.py --url http://localhost:8080 openh264 125.0
  python dom/media/tools/checkGmpBalrog.py widevine_l1 115.14.0 --staging --channel nightly beta"""

    parser = argparse.ArgumentParser(
        description="Check Balrog XML for GMP plugin updates",
        epilog=examples,
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )
    parser.add_argument(
        "plugin",
        help="which plugin: openh264, widevine, widevine_l1",
    )
    parser.add_argument("version", help="version of Firefox")
    parser.add_argument(
        "--channel", action="extend", nargs="+", help="check specific channel(s)"
    )
    parser.add_argument(
        "--list-channels", action="store_true", help="list the supported channels"
    )
    parser.add_argument(
        "--target", action="extend", nargs="+", help="check specific target(s)"
    )
    parser.add_argument(
        "--list-targets", action="store_true", help="list the supported targets"
    )
    parser.add_argument("--buildid", help="override generated build ID to be specific")
    parser.add_argument(
        "--url", help="override base URL from which to fetch the balrog configuration"
    )
    parser.add_argument(
        "--staging",
        action="store_true",
        help="using the balrog staging URL instead of production",
    )
    parser.add_argument(
        "--checkHash",
        action="store_true",
        help="download plugins and validate the size/hash",
    )
    args = parser.parse_args()

    valid_channels = ["esr", "release", "beta", "nightly", "nightlytest"]
    if args.list_channels:
        for channel in valid_channels:
            print(channel)
        return
    if args.channel is not None:
        for channel in args.channel:
            if channel not in valid_channels:
                parser.error("`%s` is invalid, see --list-channels" % channel)
                return
        channels = args.channel
    else:
        channels = valid_channels

    valid_targets = [
        "Darwin_aarch64-gcc3",
        "Darwin_x86_64-gcc3",
        "Linux_aarch64-gcc3",
        "Linux_x86-gcc3",
        "Linux_x86_64-gcc3",
        "WINNT_aarch64-msvc-aarch64",
        "WINNT_x86-msvc",
        "WINNT_x86_64-msvc",
    ]
    valid_aliases = [
        "Linux_x86_64-gcc3-asan",
        "WINNT_x86-msvc-x64",
        "WINNT_x86-msvc-x86",
        "WINNT_x86_64-msvc-x64",
        "WINNT_x86_64-msvc-x64-asan",
    ]
    if args.list_targets:
        for target in valid_targets:
            print(target)
        for target in valid_aliases:
            print("%s (alias)" % target)
        return
    if args.target is not None:
        for target in args.target:
            if target not in valid_targets and target not in valid_aliases:
                parser.error("`%s` is invalid, see --list-targets" % target)
                return
        targets = args.target
    else:
        targets = valid_targets

    if args.buildid is not None:
        if not re.match(r"^\d{14}$", args.buildid):
            parser.error("`%s` is invalid, build id must be 14 digits")
            return
        buildid = args.buildid

    else:
        buildid = datetime.today().strftime("%y%m%d%H%M%S")

    url_base = "https://aus5.mozilla.org"
    if args.staging:
        url_base = "https://stage.balrog.nonprod.cloudops.mozgcp.net"
    if args.url is not None:
        url_base = args.url
    if url_base[-1] == "/":
        url_base = url_base[:-1]
    url_base += "/update/3/GMP"

    parsed_url = urlparse(url_base)
    if parsed_url.scheme not in ("http", "https"):
        parser.error("expected http(s) scheme, got `%s`" % parsed_url.scheme)
        return
    if parsed_url.path != "/update/3/GMP":
        parser.error("expected url path of `/update/3/GMP`, got `%s`" % parsed_url.path)
        return

    if args.plugin == "openh264":
        plugin = "gmp-gmpopenh264"
    elif args.plugin == "widevine":
        plugin = "gmp-widevinecdm"
    elif args.plugin == "widevine_l1":
        plugin = "gmp-widevinecdm-l1"
    else:
        parser.error("plugin not recognized")
        return

    if not re.match(r"^\d+\.\d+(\.\d+)?$", args.version):
        parser.error("version must be of the form ###.###(.###)")
        return

    fetch_balrog_xml(
        url_base, plugin, args.version, buildid, channels, targets, args.checkHash
    )


main()
