# 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 logging
import sys
import tempfile
from pathlib import Path
from platform import uname
from shutil import copytree, unpack_archive

import mozinstall
import requests
from mach.decorators import Command, CommandArgument
from mozbuild.base import BinaryNotFoundException
from mozlog.structured import commandline

TEST_UPDATE_CHANNEL = "release-localtest"
if TEST_UPDATE_CHANNEL.startswith("release"):
    MAR_CHANNEL = "firefox-mozilla-release"
elif TEST_UPDATE_CHANNEL.startswith("beta"):
    MAR_CHANNEL = "firefox-mozilla-beta"
else:
    MAR_CHANNEL = "firefox-mozilla-central"
TEST_REGION = "en-US"
TEST_SOURCE_VERSION = "135.0.1"
FX_DOWNLOAD_DIR_URL = "https://archive.mozilla.org/pub/firefox/releases/"
APP_DIR_NAME = "fx_test"


def setup_update_argument_parser():
    from marionette_harness.runtests import MarionetteArguments
    from mozlog.structured import commandline

    parser = MarionetteArguments()
    commandline.add_logging_group(parser)

    return parser


def get_fx_executable_name(version):
    u = uname()

    if u.system == "Darwin":
        platform = "mac"
        executable_name = f"Firefox {version}.dmg"

    if u.system == "Linux":
        if "64" in u.machine:
            platform = "linux-x86_64"
        else:
            platform = "linux-x86_64"
        if int(version.split(".")[0]) < 135:
            executable_name = f"firefox-{version}.tar.bz2"
        else:
            executable_name = f"firefox-{version}.tar.xz"

    if u.system == "Windows":
        if u.machine == "ARM64":
            platform = "win64-aarch64"
        elif "64" in u.machine:
            platform = "win64"
        else:
            platform = "win32"
        executable_name = f"Firefox Setup {version}.exe"

    return platform, executable_name.replace(" ", "%20")


def get_binary_path(tempdir, **kwargs) -> str:
    # Install correct Fx and return executable location
    platform, executable_name = get_fx_executable_name(TEST_SOURCE_VERSION)

    executable_url = rf"{FX_DOWNLOAD_DIR_URL}{TEST_SOURCE_VERSION}/{platform}/{TEST_REGION}/{executable_name}"

    installer_filename = Path(tempdir, Path(executable_url).name)
    installed_app_dir = Path(tempdir, APP_DIR_NAME)
    print(f"Downloading Fx from {executable_url}...")
    response = requests.get(executable_url)
    if 199 < response.status_code < 300:
        print(f"Download successful, status {response.status_code}")
    with open(installer_filename, "wb") as fh:
        fh.write(response.content)
    fx_location = mozinstall.install(installer_filename, installed_app_dir)
    print(f"Firefox installed to {fx_location}")
    return fx_location


@Command(
    "update-test",
    category="testing",
    virtualenv_name="update",
    description="Test if the version can be updated to the latest patch successfully,",
    parser=setup_update_argument_parser,
)
@CommandArgument("--binary_path", help="Firefox executable path is needed")
def build(command_context, binary_path, **kwargs):
    tempdir = tempfile.TemporaryDirectory()
    # If we have a symlink to the tmp directory, resolve it
    tempdir_name = str(Path(tempdir.name).resolve())
    try:
        kwargs["binary"] = set_up(
            binary_path or get_binary_path(tempdir_name, **kwargs), tempdir=tempdir_name
        )
        return run_tests(
            topsrcdir=command_context.topsrcdir, tempdir=tempdir_name, **kwargs
        )
    except BinaryNotFoundException as e:
        command_context.log(
            logging.ERROR,
            "update-test",
            {"error": str(e)},
            "ERROR: {error}",
        )
        command_context.log(logging.INFO, "update-test", {"help": e.help()}, "{help}")
        return 1
    finally:
        tempdir.cleanup()


def run_tests(binary=None, topsrcdir=None, tempdir=None, **kwargs):
    from argparse import Namespace

    from marionette_harness.runtests import MarionetteHarness, MarionetteTestRunner

    args = Namespace()
    args.binary = binary
    args.logger = kwargs.pop("log", None)
    if not args.logger:
        args.logger = commandline.setup_logging(
            "Update Tests", args, {"mach": sys.stdout}
        )

    for k, v in kwargs.items():
        setattr(args, k, v)

    args.tests = [
        Path(
            topsrcdir,
            "testing/update/manifest.toml",
        )
    ]
    args.gecko_log = "-"

    parser = setup_update_argument_parser()
    parser.verify_usage(args)

    failed = MarionetteHarness(MarionetteTestRunner, args=vars(args)).run()
    if failed > 0:
        return 1
    return 0


def copy_macos_channelprefs(tempdir) -> str:
    # Copy ChannelPrefs.framework to the correct location on MacOS,
    # return the location of the Fx executable
    installed_app_dir = Path(tempdir, APP_DIR_NAME)

    bz_channelprefs_link = "https://bugzilla.mozilla.org/attachment.cgi?id=9417387"

    resp = requests.get(bz_channelprefs_link)
    download_target = Path(tempdir, "channelprefs.zip")
    unpack_target = str(download_target).rsplit(".", 1)[0]
    with open(download_target, "wb") as fh:
        fh.write(resp.content)

    unpack_archive(download_target, unpack_target)
    print(
        f"Downloaded channelprefs.zip to {download_target} and unpacked to {unpack_target}"
    )

    src = Path(tempdir, "channelprefs", TEST_UPDATE_CHANNEL)
    dst = Path(installed_app_dir, "Contents", "Frameworks")

    Path(installed_app_dir, "Firefox.app").chmod(455)  # rwx for all users

    print(f"Copying ChannelPrefs.framework from {src} to {dst}")
    copytree(
        Path(src, "ChannelPrefs.framework"),
        Path(dst, "ChannelPrefs.framework"),
        dirs_exist_ok=True,
    )

    # test against the binary that was copied to local
    fx_executable = Path(
        installed_app_dir, "Firefox.app", "Contents", "MacOS", "firefox"
    )
    return str(fx_executable)


def set_up(binary_path, tempdir):
    # Set channel prefs for all OS targets
    binary_path_str = mozinstall.get_binary(binary_path, "Firefox")
    print(f"Binary path: {binary_path_str}")
    binary_dir = Path(binary_path_str).absolute().parent

    if uname().system == "Darwin":
        return copy_macos_channelprefs(tempdir)
    else:
        with Path(binary_dir, "update-settings.ini").open("w") as f:
            f.write("[Settings]\n")
            f.write(f"ACCEPTED_MAR_CHANNEL_IDS={MAR_CHANNEL}")

        with Path(binary_dir, "defaults", "pref", "channel-prefs.js").open("w") as f:
            f.write(f'pref("app.update.channel", "{TEST_UPDATE_CHANNEL}");')

    return binary_path_str
