import re
import unittest
from pathlib import Path


ACTION_YAML = Path(__file__).resolve().parents[1] / "action.yml"
STEP_RE = re.compile(r"^    - name: (.+)$", re.M)


def step_names(source: str) -> list[str]:
    return [m.group(1).strip() for m in STEP_RE.finditer(source)]


def step_block(source: str, name: str) -> str:
    pattern = rf"    - name: {re.escape(name)}\n(?P<body>.*?)(?=\n    - name:|\Z)"
    match = re.search(pattern, source, re.DOTALL)
    if match is None:
        raise AssertionError(f"missing action step: {name}")
    return match.group("body")


class TestActionStepOrder(unittest.TestCase):
    """The plugin self-update runs before the steps that can fail a deploy,
    lands in its own commit, and the run keeps reading ONE version of the
    plugin from the snapshot it took first."""

    @classmethod
    def setUpClass(cls) -> None:
        cls.source = ACTION_YAML.read_text(encoding="utf-8")
        cls.names = step_names(cls.source)

    def test_the_snapshot_is_the_first_step(self) -> None:
        self.assertEqual(self.names[0], "Snapshot this action for the whole run")
        block = step_block(self.source, "Snapshot this action for the whole run")
        self.assertIn('cp -R "${{ github.action_path }}/." "$RUNNER_TEMP/swift-app-action/"', block)
        self.assertIn('echo "SWIFT_APP_ACTION=$RUNNER_TEMP/swift-app-action" >> "$GITHUB_ENV"', block)

    def test_the_autoupdate_and_its_commit_precede_the_version_step(self) -> None:
        order = {name: index for index, name in enumerate(self.names)}
        version = order["Resolve marketing version (project source of truth)"]
        self.assertLess(order["Stage bot-side scripts to runner temp"], version)
        self.assertLess(order["Check for app-ci update"], version)
        self.assertLess(order["Commit + push refreshed app-ci"], version)
        self.assertLess(order["Stage bot-side scripts to runner temp"],
                        order["Check for app-ci update"])
        self.assertLess(order["Check for app-ci update"],
                        order["Commit + push refreshed app-ci"])
        # ... and before every other step that talks to a store or signs.
        for later in ("Resolve credentials + auto-detect Xcode project",
                      "Stage ASC API key for authenticated REST uploads",
                      "Prepare signing (cert + keychain + per-target profiles + pbxproj)",
                      "Archive"):
            self.assertLess(order["Commit + push refreshed app-ci"], order[later], later)

    def test_the_autoupdate_commit_is_gated_like_the_check(self) -> None:
        check = step_block(self.source, "Check for app-ci update")
        commit = step_block(self.source, "Commit + push refreshed app-ci")
        for block in (check, commit):
            self.assertIn("inputs.auto-update == 'true'", block)
            self.assertIn("github.event_name == 'push'", block)
            self.assertIn("format('refs/heads/{0}', github.event.repository.default_branch)", block)
        self.assertIn('bash "$BOT_SCRIPTS_DIR/commit_bot_changes.sh"', commit)
        self.assertIn("GITHUB_REF_NAME: ${{ github.ref_name }}", commit)
        # Version source is preserved after ephemeral signing preparation.
        self.assertIn("Commit + push staged bot changes", self.names)
        self.assertLess(self.names.index("Prepare signing (cert + keychain + per-target profiles + pbxproj)"),
                        self.names.index("Commit + push staged bot changes"))

    def test_every_script_and_prompt_is_read_from_the_snapshot(self) -> None:
        lines = self.source.splitlines()
        snapshot = step_block(self.source, "Snapshot this action for the whole run")
        stray = [line for line in lines
                 if "github.action_path" in line and line.strip() not in snapshot]
        self.assertEqual(stray, [], "scripts must be read from $SWIFT_APP_ACTION")
        self.assertGreaterEqual(self.source.count('"$SWIFT_APP_ACTION/scripts/'), 10)
        self.assertEqual(self.source.count("${{ env.SWIFT_APP_ACTION }}/prompts/"), 2)
        bot = step_block(self.source, "Stage bot-side scripts to runner temp")
        self.assertIn('cp "$SWIFT_APP_ACTION/scripts/autoupdate_check.sh"', bot)
        self.assertIn('cp "$SWIFT_APP_ACTION/scripts/commit_bot_changes.sh"', bot)


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