#!/usr/bin/env python
# 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 os
import sys
import unittest
from unittest import mock

from mach.registrar import Registrar
from mozunit import main

# Ensure testing category is registered before importing mach_commands
Registrar.categories["testing"] = []
Registrar.commands_by_category["testing"] = set()
Registrar.register_category("testing", "testing", "testing")

# Add parent directory to path to import mach_commands
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
from mach_commands import (
    classname_for_test,
    flavor_for_test,
    project_for_ac,
    project_for_test,
    source_dir_for_test,
    submodule_for_test,
)


class TestAndroidMachCommands(unittest.TestCase):
    TEST_CASES = [
        # (test_path, expected_project, expected_component, expected_flavor, expected_class)
        (
            "mobile/android/geckoview/src/test/java/org/mozilla/gecko/util/GeckoBundleTest.java",
            "geckoview",
            None,
            "unit",
            "org.mozilla.gecko.util.GeckoBundleTest",
        ),
        (
            "mobile/android/android-components/components/concept/engine/src/test/java/mozilla/components/concept/engine/EngineTest.kt",
            "android-components",
            "concept-engine",
            "unit",
            "mozilla.components.concept.engine.EngineTest",
        ),
        (
            "mobile/android/android-components/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineTest.kt",
            "android-components",
            "browser-engine-gecko",
            "unit",
            "mozilla.components.browser.engine.gecko.GeckoEngineTest",
        ),
        (
            "mobile/android/fenix/app/src/test/java/org/mozilla/fenix/home/HomeFragmentTest.kt",
            "fenix",
            None,
            "unit",
            "org.mozilla.fenix.home.HomeFragmentTest",
        ),
        (
            "mobile/android/focus-android/app/src/test/java/org/mozilla/focus/components/EngineProviderTest.kt",
            "focus-android",
            None,
            "unit",
            "org.mozilla.focus.components.EngineProviderTest",
        ),
        (
            "mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/components/MenuItemTest.kt",
            "fenix",
            None,
            "android",
            "org.mozilla.fenix.components.MenuItemTest",
        ),
        (
            "mobile/android/fenix/app/longfox/src/test/kotlin/org/mozilla/fenix/longfox/GameStateTest.kt",
            "fenix",
            None,
            "unit",
            "org.mozilla.fenix.longfox.GameStateTest",
        ),
    ]

    def cleanup_gradle_args(self, args):
        """Given a list of arguments for a gradle invocation, remove those that are not
        interesting for this test suite to simplify matching."""
        IGNORE = ["-q", "--rerun"]
        return [x for x in args if x not in IGNORE]

    def test_classname_for_test(self):
        """Test extraction of class name from test path."""
        for test_path, _, _, _, expected in self.TEST_CASES:
            with self.subTest(test_path=test_path):
                if flavor_for_test(test_path) == "unit":
                    result = classname_for_test(
                        test_path, source_dir_for_test(test_path)
                    )
                else:
                    result = classname_for_test(
                        test_path, source_dir_for_test(test_path, "androidTest")
                    )
                self.assertEqual(result, expected)

    def test_project_for_test(self):
        """Test extraction of project name from test path."""
        PROJECT_PREFIX = "mobile/android"

        for test_path, expected, _, _, _ in self.TEST_CASES:
            with self.subTest(project=expected):
                result = project_for_test(test_path, PROJECT_PREFIX)
                self.assertEqual(result, expected)

    def test_project_for_ac(self):
        """Test extraction of android-components subproject name."""
        COMPONENT_PREFIX = "mobile/android/android-components/components"
        TEST_PATH_PREFIX = "src/test/java"
        for test_path, project, expected, _, _ in self.TEST_CASES:
            if project != "android-components":
                continue
            with self.subTest(component=expected):
                result = project_for_ac(test_path, COMPONENT_PREFIX, TEST_PATH_PREFIX)
                self.assertEqual(result, expected)

    def test_flavor_for_test(self):
        """Test extraction of test flavor from test path."""
        for test_path, _, _, expected, _ in self.TEST_CASES:
            with self.subTest(flavor=expected):
                result = flavor_for_test(test_path)
                self.assertEqual(result, expected)

    def test_android_test_implicit_subproject(self):
        """Test that run_android_test derives subproject if a specific test is requested."""
        from mach_commands import run_android_test

        command_context = mock.MagicMock()
        mock_dispatch = command_context._mach_context.commands.dispatch = (
            mock.MagicMock(return_value=0)
        )

        TEST_PATH = "mobile/android/focus-android/app/src/test/java/org/mozilla/focus/components/EngineProviderTest.kt"
        run_android_test(command_context, subproject=None, test=TEST_PATH)

        mock_dispatch.assert_called_once()
        gradle_args = mock_dispatch.call_args[1]["args"]
        self.assertEqual(
            self.cleanup_gradle_args(gradle_args),
            [
                "-p",
                os.path.join("mobile", "android", "focus-android", "app"),
                ":app:testFocusDebugUnitTest",
                "--tests",
                "org.mozilla.focus.components.EngineProviderTest",
            ],
        )

    def test_run_android_test_fenix(self):
        """Test that run_android_test calls dispatch with correct arguments for fenix."""
        from mach_commands import run_android_test

        command_context = mock.MagicMock()
        mock_dispatch = command_context._mach_context.commands.dispatch = (
            mock.MagicMock(return_value=0)
        )

        run_android_test(
            command_context,
            subproject="fenix",
        )

        mock_dispatch.assert_called_once()
        gradle_args = mock_dispatch.call_args[1]["args"]

        self.assertEqual(
            self.cleanup_gradle_args(gradle_args),
            [
                "-p",
                os.path.join("mobile", "android", "fenix", "app"),
                "testDebugUnitTest",
            ],
        )

    def test_run_android_test_androidTest(self):
        """Test that run_android_test can run instrumented test types."""
        from mach_commands import run_android_test

        command_context = mock.MagicMock()
        mock_dispatch = command_context._mach_context.commands.dispatch = (
            mock.MagicMock(return_value=0)
        )

        TEST_PATH = "mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/components/MenuItemTest.kt"
        run_android_test(
            command_context,
            subproject=None,
            test=TEST_PATH,
        )

        mock_dispatch.assert_called_once()
        gradle_args = mock_dispatch.call_args[1]["args"]

        self.assertEqual(
            self.cleanup_gradle_args(gradle_args),
            [
                "-p",
                os.path.join("mobile", "android", "fenix", "app"),
                ":app:connectedDebugAndroidTest",
                "-Pandroid.testInstrumentationRunnerArguments.class=org.mozilla.fenix.components.MenuItemTest",
            ],
        )

    def test_run_android_test_gradle_variant(self):
        """Test that run_android_test handles --gradle-variant."""
        from mach_commands import run_android_test

        command_context = mock.MagicMock()
        mock_dispatch = command_context._mach_context.commands.dispatch = (
            mock.MagicMock(return_value=0)
        )

        run_android_test(
            command_context,
            subproject="geckoview",
            gradle_variant="Release",
        )

        mock_dispatch.assert_called_once()
        gradle_args = mock_dispatch.call_args[1]["args"]

        self.assertEqual(
            self.cleanup_gradle_args(gradle_args),
            [
                "-p",
                os.path.join("mobile", "android", "geckoview"),
                "testReleaseUnitTest",
            ],
        )

    def test_run_android_test_flavor(self):
        """Test that run_android_test handles --flavor."""
        from mach_commands import run_android_test

        command_context = mock.MagicMock()
        mock_dispatch = command_context._mach_context.commands.dispatch = (
            mock.MagicMock(return_value=0)
        )

        run_android_test(
            command_context,
            subproject="geckoview",
            flavor="both",
        )

        mock_dispatch.assert_called_once()
        gradle_args = mock_dispatch.call_args[1]["args"]

        self.assertEqual(
            self.cleanup_gradle_args(gradle_args),
            [
                "-p",
                os.path.join("mobile", "android", "geckoview"),
                "testDebugUnitTest",
                "connectedDebugAndroidTest",
            ],
        )

    def test_submodule_for_test(self):
        """Test detection of nested sub-modules."""
        SUBDIR = os.path.join("mobile", "android", "fenix", "app")
        self.assertIsNone(
            submodule_for_test(
                "mobile/android/fenix/app/src/test/java/org/mozilla/fenix/home/HomeFragmentTest.kt",
                SUBDIR,
            )
        )
        self.assertEqual(
            submodule_for_test(
                "mobile/android/fenix/app/longfox/src/test/kotlin/org/mozilla/fenix/longfox/GameStateTest.kt",
                SUBDIR,
            ),
            "longfox",
        )

    def test_run_android_test_longfox(self):
        """Test that run_android_test handles longfox submodule tests."""
        from mach_commands import run_android_test

        command_context = mock.MagicMock()
        mock_dispatch = command_context._mach_context.commands.dispatch = (
            mock.MagicMock(return_value=0)
        )

        test_objects = [
            {
                "name": "mobile/android/fenix/app/src/test/java/org/mozilla/fenix/home/HomeFragmentTest.kt",
            },
            {
                "name": "mobile/android/fenix/app/longfox/src/test/kotlin/org/mozilla/fenix/longfox/GameStateTest.kt",
            },
        ]

        run_android_test(
            command_context,
            subproject="fenix",
            test_objects=test_objects,
        )

        gradle_args = mock_dispatch.call_args[1]["args"]
        self.assertEqual(
            self.cleanup_gradle_args(gradle_args),
            [
                "-p",
                os.path.join("mobile", "android", "fenix", "app"),
                ":app:testDebugUnitTest",
                "--tests",
                "org.mozilla.fenix.home.HomeFragmentTest",
                ":app:longfox:testDebugUnitTest",
                "--tests",
                "org.mozilla.fenix.longfox.GameStateTest",
            ],
        )

    def test_run_android_test_with_multi_component(self):
        """Test that multiple android-component tests resolve the their component projects"""
        from mach_commands import run_android_test

        command_context = mock.MagicMock()
        mock_dispatch = mock.MagicMock(return_value=0)
        command_context._mach_context.commands.dispatch = mock_dispatch

        test_objects = [
            {
                "name": "mobile/android/android-components/components/concept/engine/src/test/java/mozilla/components/concept/engine/EngineTest.kt",
            },
            {
                "name": "mobile/android/android-components/components/concept/engine/src/test/java/mozilla/components/concept/engine/EngineViewTest.kt",
            },
            {
                "name": "mobile/android/android-components/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineTest.kt",
            },
            {
                "name": "mobile/android/android-components/components/browser/engine-gecko/src/androidTest/java/mozilla/components/browser/engine/gecko/fetch/geckoview/GeckoViewFetchTestCases.kt",
            },
        ]

        run_android_test(
            command_context,
            subproject="android-components",
            test_objects=test_objects,
        )

        gradle_args = mock_dispatch.call_args[1]["args"]
        self.assertEqual(
            self.cleanup_gradle_args(gradle_args),
            [
                "-p",
                os.path.join("mobile", "android", "android-components"),
                ":components:concept-engine:testDebugUnitTest",
                "--tests",
                "mozilla.components.concept.engine.EngineTest",
                "--tests",
                "mozilla.components.concept.engine.EngineViewTest",
                ":components:browser-engine-gecko:testDebugUnitTest",
                "--tests",
                "mozilla.components.browser.engine.gecko.GeckoEngineTest",
                ":components:browser-engine-gecko:connectedDebugAndroidTest",
                "-Pandroid.testInstrumentationRunnerArguments.class=mozilla.components.browser.engine.gecko.fetch.geckoview.GeckoViewFetchTestCases",
            ],
        )


if __name__ == "__main__":
    main()
