# 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 json
import logging
import os
import subprocess
import tempfile
import zipfile
from contextlib import nullcontext as does_not_raise
from io import StringIO
from unittest.mock import Mock

import mozunit
import pytest

from mozbuild.repackaging import deb, desktop_file

_APPLICATION_INI_CONTENT = """[App]
Vendor=Mozilla
Name=Firefox
RemotingName=firefox-nightly-try
CodeName=Firefox Nightly
BuildID=20230222000000
"""

_APPLICATION_INI_CONTENT_DATA = {
    "name": "Firefox",
    "display_name": "Firefox Nightly",
    "vendor": "Mozilla",
    "remoting_name": "firefox-nightly-try",
    "build_id": "20230222000000",
}

ZH_TW_FTL = """\
# 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/.


# These messages are used by the Firefox ".desktop" file on Linux.
# https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html

# The entry name is the label on the desktop icon, among other things.
desktop-entry-name = { -brand-shortcut-name }
# The comment usually appears as a tooltip when hovering over application menu entry.
desktop-entry-comment-1 = 瀏覽全球資訊網
desktop-entry-generic-name = 網頁瀏覽器
# Keywords are search terms used to find this application.
# The string is a list of keywords separated by semicolons:
# - Do NOT replace semicolons with other punctuation signs.
# - The list MUST end with a semicolon.
desktop-entry-keywords = 網際網路;網路;瀏覽器;網頁;上網;Internet;WWW;Browser;Web;Explorer;

## Actions are visible in a context menu after right clicking the
## taskbar icon, possibly other places depending on the environment.

desktop-action-new-window-name = 開新視窗
desktop-action-new-private-window-name = 開新隱私視窗
"""

NIGHTLY_DESKTOP_ENTRY_FILE_TEXT = """\
[Desktop Entry]
Version=1.0
Type=Application
Exec=firefox-nightly %u
Terminal=false
X-MultipleArgs=false
Icon=firefox-nightly
StartupWMClass=firefox-nightly
Categories=GNOME;GTK;Network;WebBrowser;
MimeType=application/json;application/pdf;application/rdf+xml;application/rss+xml;application/x-xpinstall;application/xhtml+xml;application/xml;audio/flac;audio/ogg;audio/webm;image/avif;image/gif;image/jpeg;image/png;image/svg+xml;image/webp;text/html;text/xml;video/ogg;video/webm;x-scheme-handler/chrome;x-scheme-handler/http;x-scheme-handler/https;x-scheme-handler/mailto;
StartupNotify=true
Actions=new-window;new-private-window;open-profile-manager;
Name=en-US-desktop-entry-name
Name[zh_TW]=zh-TW-desktop-entry-name
Comment=en-US-desktop-entry-comment-1
Comment[zh_TW]=zh-TW-desktop-entry-comment-1
GenericName=en-US-desktop-entry-generic-name
GenericName[zh_TW]=zh-TW-desktop-entry-generic-name
Keywords=en-US-desktop-entry-keywords
Keywords[zh_TW]=zh-TW-desktop-entry-keywords
X-GNOME-FullName=en-US-desktop-entry-x-gnome-full-name-1
X-GNOME-FullName[zh_TW]=zh-TW-desktop-entry-x-gnome-full-name-1

[Desktop Action new-window]
Exec=firefox-nightly --new-window %u
Name=en-US-desktop-action-new-window-name
Name[zh_TW]=zh-TW-desktop-action-new-window-name

[Desktop Action new-private-window]
Exec=firefox-nightly --private-window %u
Name=en-US-desktop-action-new-private-window-name
Name[zh_TW]=zh-TW-desktop-action-new-private-window-name

[Desktop Action open-profile-manager]
Exec=firefox-nightly --ProfileManager
Name=en-US-desktop-action-open-profile-manager
Name[zh_TW]=zh-TW-desktop-action-open-profile-manager
"""

DEVEDITION_DESKTOP_ENTRY_FILE_TEXT = """\
[Desktop Entry]
Version=1.0
Type=Application
Exec=firefox-devedition %u
Terminal=false
X-MultipleArgs=false
Icon=firefox-devedition
StartupWMClass=firefox-dev
Categories=GNOME;GTK;Network;WebBrowser;
MimeType=application/json;application/pdf;application/rdf+xml;application/rss+xml;application/x-xpinstall;application/xhtml+xml;application/xml;audio/flac;audio/ogg;audio/webm;image/avif;image/gif;image/jpeg;image/png;image/svg+xml;image/webp;text/html;text/xml;video/ogg;video/webm;x-scheme-handler/chrome;x-scheme-handler/http;x-scheme-handler/https;x-scheme-handler/mailto;
StartupNotify=true
Actions=new-window;new-private-window;open-profile-manager;
Name=en-US-desktop-entry-name
Name[zh_TW]=zh-TW-desktop-entry-name
Comment=en-US-desktop-entry-comment-1
Comment[zh_TW]=zh-TW-desktop-entry-comment-1
GenericName=en-US-desktop-entry-generic-name
GenericName[zh_TW]=zh-TW-desktop-entry-generic-name
Keywords=en-US-desktop-entry-keywords
Keywords[zh_TW]=zh-TW-desktop-entry-keywords
X-GNOME-FullName=en-US-desktop-entry-x-gnome-full-name-1
X-GNOME-FullName[zh_TW]=zh-TW-desktop-entry-x-gnome-full-name-1

[Desktop Action new-window]
Exec=firefox-devedition --new-window %u
Name=en-US-desktop-action-new-window-name
Name[zh_TW]=zh-TW-desktop-action-new-window-name

[Desktop Action new-private-window]
Exec=firefox-devedition --private-window %u
Name=en-US-desktop-action-new-private-window-name
Name[zh_TW]=zh-TW-desktop-action-new-private-window-name

[Desktop Action open-profile-manager]
Exec=firefox-devedition --ProfileManager
Name=en-US-desktop-action-open-profile-manager
Name[zh_TW]=zh-TW-desktop-action-open-profile-manager
"""


def test_generate_deb_desktop_entry_file_text(monkeypatch):

    def check_call(cmd=[], cwd=None):
        assert len(cmd) > 1
        assert cmd[0] == "git"
        assert cmd[1] in ["init", "remote", "fetch", "reset"]

        if cmd[1] == "init":
            assert cwd is None
            test_generate_deb_desktop_entry_file_text.l10n_repo_clone = cmd[2]

        if cmd[1] == "remote":
            assert cwd is not None
            assert cmd[2] == "add"
            assert cmd[3] == "origin"
            assert cmd[4] == "https://github.com/mozilla-l10n/firefox-l10n"

        if cmd[1] == "fetch":
            assert cwd is not None
            assert cmd[2] == "--no-progress"
            assert cmd[3] == "--depth=1"
            assert cmd[4] == "origin"
            assert cmd[5] == "default"

        if cmd[1] == "reset":
            assert cwd is not None
            assert cmd[2] == "--hard"
            assert cmd[3] == "FETCH_HEAD"

            desktop_zhTW_file = os.path.join(
                cwd, "zh-TW", "browser", "browser", "linuxDesktopEntry.ftl"
            )
            os.makedirs(os.path.dirname(desktop_zhTW_file))
            with open(desktop_zhTW_file, "w", encoding="utf-8") as zhTW:
                zhTW.write(ZH_TW_FTL)

    monkeypatch.setattr(desktop_file.subprocess, "check_call", check_call)

    output_stream = StringIO()
    logger = logging.getLogger("mozbuild:test:repackaging")
    logger.setLevel(logging.DEBUG)
    stream_handler = logging.StreamHandler(output_stream)
    logger.addHandler(stream_handler)

    def log(level, action, params, format_str):
        logger.log(
            level,
            format_str.format(**params),
            extra={"action": action, "params": params},
        )

    def fluent_localization(locales, resources, loader):
        def format_value(resource):
            return f"{locales[0]}-{resource}"

        return Mock(**{"format_value": format_value})

    fluent_resource_loader = Mock()

    monkeypatch.setattr(
        desktop_file.json,
        "load",
        lambda f: {"zh-TW": {"platforms": ["linux"], "revision": "default"}},
    )

    build_variables = {
        "PKG_NAME": "firefox-nightly",
        "Icon": "firefox-nightly",
        "REMOTING_NAME": "firefox-nightly",
    }
    release_product = "firefox"
    release_type = "nightly"

    def mock_copy(source_path, destination_path):
        assert source_path in [
            os.path.join(
                "browser", "locales", "en-US", "browser", "linuxDesktopEntry.ftl"
            ),
            os.path.join(
                "browser", "branding", "nightly", "locales", "en-US", "brand.ftl"
            ),
            os.path.join(
                "browser", "branding", "aurora", "locales", "en-US", "brand.ftl"
            ),
            os.path.join(
                test_generate_deb_desktop_entry_file_text.l10n_repo_clone,
                "zh-TW",
                "browser",
                "browser",
                "linuxDesktopEntry.ftl",
            ),
        ]

        if source_path == os.path.join(
            "browser", "locales", "en-US", "browser", "linuxDesktopEntry.ftl"
        ):
            assert os.path.join("en-US", "linuxDesktopEntry.ftl") in destination_path

        if source_path in [
            os.path.join(
                "browser", "branding", "nightly", "locales", "en-US", "brand.ftl"
            ),
            os.path.join(
                "browser", "branding", "aurora", "locales", "en-US", "brand.ftl"
            ),
        ]:
            destination_path_subdir = os.path.sep.join(
                destination_path.split(os.path.sep)[-2:]
            )
            assert destination_path_subdir in [
                os.path.join("en-US", "brand.ftl"),
                os.path.join("zh-TW", "brand.ftl"),
            ]

        with open(source_path, encoding="utf-8") as src:
            with open(destination_path, "w", encoding="utf-8") as dest:
                dest.write(src.read())

    monkeypatch.setattr(desktop_file.shutil, "copyfile", mock_copy)

    desktop_entry_file_text = desktop_file.generate_browser_desktop_entry_file_text(
        log,
        build_variables,
        release_product,
        release_type,
        fluent_localization,
        fluent_resource_loader,
    )

    assert test_generate_deb_desktop_entry_file_text.l10n_repo_clone is not None

    assert desktop_entry_file_text == NIGHTLY_DESKTOP_ENTRY_FILE_TEXT

    build_variables = {
        "PKG_NAME": "firefox-devedition",
        "Icon": "firefox-devedition",
        "REMOTING_NAME": "firefox-dev",
    }
    release_product = "devedition"
    release_type = "beta"

    desktop_entry_file_text = desktop_file.generate_browser_desktop_entry_file_text(
        log,
        build_variables,
        release_product,
        release_type,
        fluent_localization,
        fluent_resource_loader,
    )

    assert desktop_entry_file_text == DEVEDITION_DESKTOP_ENTRY_FILE_TEXT

    def outage(cmd=[], cwd=None):
        raise subprocess.CalledProcessError(cmd=cmd, returncode=42)

    monkeypatch.setattr(desktop_file.subprocess, "check_call", outage)

    with pytest.raises(subprocess.CalledProcessError):
        desktop_entry_file_text = desktop_file.generate_browser_desktop_entry_file_text(
            log,
            build_variables,
            release_product,
            release_type,
            fluent_localization,
            fluent_resource_loader,
        )


@pytest.mark.parametrize(
    "does_path_exits, expectation",
    (
        (True, does_not_raise()),
        (False, pytest.raises(deb.NoDebPackageFound)),
    ),
)
def test_generate_deb_archive(
    monkeypatch,
    does_path_exits,
    expectation,
):
    monkeypatch.setattr(deb, "_get_command", lambda _: ["mock_command"])
    monkeypatch.setattr(deb.subprocess, "check_call", lambda *_, **__: None)

    def mock_exists(path):
        assert path == "/target_dir/firefox_111.0_amd64.deb"
        return does_path_exits

    monkeypatch.setattr(deb.os.path, "exists", mock_exists)

    def mock_move(source_path, destination_path):
        assert source_path == "/target_dir/firefox_111.0_amd64.deb"
        assert destination_path == "/output/target.deb"

    monkeypatch.setattr(deb.shutil, "move", mock_move)

    with expectation:
        deb._generate_deb_archive(
            source_dir="/source_dir",
            target_dir="/target_dir",
            output_file_path="/output/target.deb",
            build_variables={
                "PKG_NAME": "firefox",
                "PKG_VERSION": "111.0",
            },
            arch="x86_64",
        )


@pytest.mark.parametrize(
    "arch, is_chroot_available, expected",
    (
        (
            "all",
            True,
            [
                "chroot",
                "/srv/jessie-amd64",
                "bash",
                "-c",
                "cd /tmp/*/source; dpkg-buildpackage -us -uc -b",
            ],
        ),
        ("all", False, ["dpkg-buildpackage", "-us", "-uc", "-b"]),
        (
            "x86",
            True,
            [
                "chroot",
                "/srv/jessie-i386",
                "bash",
                "-c",
                "cd /tmp/*/source; dpkg-buildpackage -us -uc -b --host-arch=i386",
            ],
        ),
        ("x86", False, ["dpkg-buildpackage", "-us", "-uc", "-b", "--host-arch=i386"]),
        (
            "x86_64",
            True,
            [
                "chroot",
                "/srv/jessie-amd64",
                "bash",
                "-c",
                "cd /tmp/*/source; dpkg-buildpackage -us -uc -b --host-arch=amd64",
            ],
        ),
        (
            "x86_64",
            False,
            ["dpkg-buildpackage", "-us", "-uc", "-b", "--host-arch=amd64"],
        ),
    ),
)
def test_get_command(monkeypatch, arch, is_chroot_available, expected):
    monkeypatch.setattr(deb, "_is_chroot_available", lambda _: is_chroot_available)
    assert deb._get_command(arch) == expected


@pytest.mark.parametrize(
    "arch, does_dir_exist, expected_path, expected_result",
    (
        ("all", False, "/srv/jessie-amd64", False),
        ("all", True, "/srv/jessie-amd64", True),
        ("x86", False, "/srv/jessie-i386", False),
        ("x86_64", False, "/srv/jessie-amd64", False),
        ("x86", True, "/srv/jessie-i386", True),
        ("x86_64", True, "/srv/jessie-amd64", True),
    ),
)
def test_is_chroot_available(
    monkeypatch, arch, does_dir_exist, expected_path, expected_result
):
    def _mock_is_dir(path):
        assert path == expected_path
        return does_dir_exist

    monkeypatch.setattr(deb.os.path, "isdir", _mock_is_dir)
    assert deb._is_chroot_available(arch) == expected_result


@pytest.mark.parametrize(
    "arch, expected",
    (
        ("all", "/srv/jessie-amd64"),
        ("x86", "/srv/jessie-i386"),
        ("x86_64", "/srv/jessie-amd64"),
    ),
)
def test_get_chroot_path(arch, expected):
    assert deb._get_chroot_path(arch) == expected


_MANIFEST_JSON_DATA = {
    "langpack_id": "fr",
    "manifest_version": 2,
    "browser_specific_settings": {
        "gecko": {
            "id": "langpack-fr@devedition.mozilla.org",
            "strict_min_version": "112.0a1",
            "strict_max_version": "112.0a1",
        }
    },
    "name": "Language: Français (French)",
    "description": "Firefox Developer Edition Language Pack for Français (fr) – French",
    "version": "112.0.20230227.181253",
    "languages": {
        "fr": {
            "version": "20230223164410",
            "chrome_resources": {
                "app-marketplace-icons": "browser/chrome/browser/locale/fr/app-marketplace-icons/",
                "branding": "browser/chrome/fr/locale/branding/",
                "browser": "browser/chrome/fr/locale/browser/",
                "browser-region": "browser/chrome/fr/locale/browser-region/",
                "devtools": "browser/chrome/fr/locale/fr/devtools/client/",
                "devtools-shared": "browser/chrome/fr/locale/fr/devtools/shared/",
                "alerts": "chrome/fr/locale/fr/alerts/",
                "autoconfig": "chrome/fr/locale/fr/autoconfig/",
                "global": "chrome/fr/locale/fr/global/",
                "global-platform": {
                    "macosx": "chrome/fr/locale/fr/global-platform/mac/",
                    "linux": "chrome/fr/locale/fr/global-platform/unix/",
                    "android": "chrome/fr/locale/fr/global-platform/unix/",
                    "win": "chrome/fr/locale/fr/global-platform/win/",
                },
                "mozapps": "chrome/fr/locale/fr/mozapps/",
                "necko": "chrome/fr/locale/fr/necko/",
                "passwordmgr": "chrome/fr/locale/fr/passwordmgr/",
                "pdf.js": "chrome/fr/locale/pdfviewer/",
                "pipnss": "chrome/fr/locale/fr/pipnss/",
                "pippki": "chrome/fr/locale/fr/pippki/",
                "places": "chrome/fr/locale/fr/places/",
                "weave": "chrome/fr/locale/fr/services/",
            },
        }
    },
    "sources": {"browser": {"base_path": "browser/"}},
    "author": "mozfr.org (contributors: L’équipe francophone)",
}


def test_extract_langpack_metadata():
    with tempfile.TemporaryDirectory() as d:
        langpack_path = os.path.join(d, "langpack.xpi")
        with zipfile.ZipFile(langpack_path, "w") as zip:
            zip.writestr("manifest.json", json.dumps(_MANIFEST_JSON_DATA))

        assert deb._extract_langpack_metadata(langpack_path) == _MANIFEST_JSON_DATA


if __name__ == "__main__":
    mozunit.main()
