#!/usr/bin/env python3
"""The resolved versionCode must actually reach a native Gradle build.

The plugin resolves a versionCode that cannot collide with what Play already
holds, but a native app declares its own in build.gradle. resolve_android.py
rewrites that literal when there is one; gowalk-public/carsharing-android has

    ext.code = 31
    versionCode code

which has no literal to rewrite, so the bundle shipped 31 and Play rejected it
with "Version code 31 has already been used" — including the no-auto-submit
retry, because the number was baked into the bundle.

The fix is an init script applied to the Gradle invocation. These tests pin the
wiring, since nothing else in the suite can run Gradle.
"""

from __future__ import annotations

import re
import unittest
from pathlib import Path

ACTION_DIR = Path(__file__).resolve().parent.parent
ACTION_YML = ACTION_DIR / "action.yml"
INIT_SCRIPT = ACTION_DIR / "scripts" / "version_override.init.gradle"


def gradle_build_step() -> str:
    """The `Build release Android App Bundle with Gradle` step, as raw text."""
    text = ACTION_YML.read_text()
    start = text.index("- name: Build release Android App Bundle with Gradle")
    rest = text[start + 1 :]
    end = rest.find("\n    - name:")
    return rest if end == -1 else rest[:end]


class VersionOverrideWiringTest(unittest.TestCase):
    def test_the_init_script_ships_with_the_action(self) -> None:
        self.assertTrue(INIT_SCRIPT.is_file())

    def test_the_gradle_build_applies_the_init_script(self) -> None:
        step = gradle_build_step()

        self.assertIn("--init-script", step)
        self.assertIn("scripts/version_override.init.gradle", step)
        # github.action_path, not a relative path: the step runs with
        # working-directory set to the Gradle root, which may be android/.
        self.assertIn("${{ github.action_path }}", step)

    def test_the_gradle_build_passes_the_resolved_build_name(self) -> None:
        step = gradle_build_step()

        self.assertIn("ANDROID_BUILD_NAME: ${{ inputs.build-name }}", step)

    def test_the_flutter_build_is_left_alone(self) -> None:
        # The Flutter path already honours the resolved number through
        # --build-number and must not be handed a Gradle init script.
        text = ACTION_YML.read_text()
        start = text.index("- name: Build release Android App Bundle\n")
        flutter_step = text[start : text.index("# --- Native Gradle", start)]

        self.assertIn("--build-number", flutter_step)
        self.assertNotIn("--init-script", flutter_step)

    def test_only_the_gradle_build_gets_the_init_script(self) -> None:
        # Notably not `./gradlew test`: a unit-test run has no versionCode to
        # get wrong, and the narrower the override's blast radius the better.
        self.assertEqual(ACTION_YML.read_text().count("--init-script"), 1)


class InitScriptTest(unittest.TestCase):
    """Guard rails that keep the override from touching a build it should not."""

    def setUp(self) -> None:
        self.source = INIT_SCRIPT.read_text()

    def test_reads_the_values_the_action_exports(self) -> None:
        self.assertIn("ANDROID_BUILD_NUMBER", self.source)
        self.assertIn("ANDROID_BUILD_NAME", self.source)

    def test_only_touches_application_modules(self) -> None:
        self.assertIn("com.android.application", self.source)

    def test_does_nothing_when_neither_value_is_set(self) -> None:
        # An unset variable means "no opinion" — the project's own value stands.
        self.assertRegex(
            self.source, r"if \(versionCode != null \|\| versionName != null\)"
        )

    def test_a_bad_build_number_warns_instead_of_failing_the_build(self) -> None:
        self.assertIn("NumberFormatException", self.source)
        self.assertIn("::warning::", self.source)

    def test_supports_agp_before_and_after_the_variant_api(self) -> None:
        self.assertIn("androidComponents", self.source)
        self.assertIn("versionCodeOverride", self.source)

    def test_does_not_use_the_removed_injected_properties(self) -> None:
        # android.injected.version.code / .name were removed in AGP 7.3 and are
        # silently ignored by every AGP 8.x app in the fleet. Reaching for them
        # would look like a fix and change nothing. The init script's header
        # explains all this, so only the code is checked here.
        code = "\n".join(
            line
            for line in self.source.splitlines()
            if not line.lstrip().startswith("//")
        )

        self.assertNotIn("android.injected.version", code)
        self.assertNotIn("android.injected.version", gradle_build_step())


class NonLiteralVersionCodeTest(unittest.TestCase):
    """The exact shape that defeated the source rewrite."""

    def test_the_carsharing_build_file_has_no_literal_to_rewrite(self) -> None:
        import tempfile

        from android_config import set_gradle_version

        with tempfile.TemporaryDirectory() as temp:
            build_file = Path(temp) / "build.gradle"
            build_file.write_text(
                "android {\n"
                "    defaultConfig {\n"
                "        ext.code = 31\n"
                "        versionCode code\n"
                '        versionName "1.${code}"\n'
                "    }\n"
                "}\n"
            )

            applied = set_gradle_version(build_file, 507)

            # Nothing to rewrite, so the checkout still says 31 — which is
            # precisely why the build needs the Variant API override.
            self.assertFalse(applied["version_code"])
            self.assertIn("ext.code = 31", build_file.read_text())

    def test_resolve_android_no_longer_calls_that_a_warning(self) -> None:
        # It used to emit ::warning::, which annotated the run as a problem
        # while the real override was about to succeed.
        source = (ACTION_DIR / "scripts" / "resolve_android.py").read_text()
        body = source[source.index("def apply_gradle_version") : source.index("def flutter_root")]

        self.assertNotIn("::warning::", body)
        self.assertIn("Variant API", body)


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