#!/usr/bin/env python3
"""
Tests for ``decide_for_version`` row-matching scenarios.

Covers what happens when the project's MARKETING_VERSION matches (or
doesn't match) an existing ASC row:

  * empty ASC -> CREATE
  * editable PFS at target -> REUSE
  * steady-state TestFlight (target == combined_floor with editable
    match) -> REUSE
  * terminal collision at target -> SystemExit(2)
    (the auto-roll lock-in test for terminal-but-autobump-enabled lives
    in test_manage_marketing_version_autoroll.py)
  * in-review row at target -> TESTFLIGHT_ONLY (the App Store slot is
    locked; the build goes to TestFlight under the locked version). The
    sibling-in-review case lives in test_mmv_testflight_only.py.
  * DEVELOPER_REJECTED at target -> REUSE (developer pulled it from
    review; safe to mutate)

Floor-vs-target scenarios live in
``test_manage_marketing_version_floor.py``. main() / env-validation
scenarios stay in ``test_manage_marketing_version.py``. Split out in
round 8 to keep each test file under the 10-functions-per-file cap.
"""

from __future__ import annotations

import os
import sys
import unittest
from pathlib import Path
from unittest import mock

sys.path.insert(0, str(Path(__file__).resolve().parent))

from tests_common import (  # noqa: E402
    _assert_decide_exits,
    _assert_mentions_build_setting_source,
    _run_decide_for_version,
    _v,
)


def setUpModule() -> None:
    """Pin auto-bump to 'none' for the whole match-tests module.

    The terminal-at-target tests assert the historical SystemExit(2)
    reject behavior. With auto-bump enabled
    (the default 'patch'), the round-7 auto-roll would CREATE the
    next patch on TERMINAL collisions, which is a different code path
    covered by ``test_manage_marketing_version_autoroll.py``."""
    global _ENV_PATCH
    _ENV_PATCH = mock.patch.dict(
        os.environ, {"MARKETING_VERSION_AUTO_BUMP": "none"}, clear=False,
    )
    _ENV_PATCH.start()


def tearDownModule() -> None:
    _ENV_PATCH.stop()


class MatchAtTargetTests(unittest.TestCase):
    """``decide_for_version`` row-matching at the target versionString."""

    def test_target_1_0_0_empty_asc_creates(self):
        """Scenario 1: target 1.0.0 + ASC empty -> CREATE."""
        result, stderr, calls = _run_decide_for_version(
            "1.0.0", versions=[], ground_truth=None,
        )
        self.assertEqual(result["decision"], "CREATE")
        self.assertEqual(result["versionString"], "1.0.0")
        self.assertEqual(result["appStoreVersionId"], "new-created-id")
        self.assertEqual(len(calls), 1)
        self.assertEqual(calls[0].args[1], "1.0.0")
        self.assertIn("decision=CREATE", stderr)

    def test_target_matches_existing_editable_reuses(self):
        """Scenario 2: target 1.0.0 + editable PFS at 1.0.0 -> REUSE id."""
        editable = _v("1.0.0", "PREPARE_FOR_SUBMISSION", vid="ed-100")
        result, stderr, calls = _run_decide_for_version(
            "1.0.0", versions=[editable], ground_truth=None,
        )
        self.assertEqual(result["decision"], "REUSE")
        self.assertEqual(result["versionString"], "1.0.0")
        self.assertEqual(result["appStoreVersionId"], "ed-100")
        self.assertEqual(calls, [])
        self.assertIn("decision=REUSE", stderr)
        self.assertIn("matches MARKETING_VERSION", stderr)

    def test_reuse_when_target_equals_combined_floor_steady_state(self):
        """Steady-state TestFlight iteration: project still sits at 1.0.0,
        ASC has the editable PFS row at 1.0.0 AND the prior TestFlight
        upload registered the same version under preReleaseVersions
        (combined_floor=1.0.0). REUSE must succeed -- the cross-check is
        not allowed to compare the target to itself."""
        editable = _v("1.0.0", "PREPARE_FOR_SUBMISSION", vid="ed-100")
        per_source = {
            "appStoreVersions": "1.0.0",
            "preReleaseVersions": "1.0.0",
            "buildsViaPreRelease": "1.0.0",
        }
        result, stderr, calls = _run_decide_for_version(
            "1.0.0", versions=[editable],
            ground_truth="1.0.0", combined_floor="1.0.0",
            per_source=per_source,
        )
        self.assertEqual(result["decision"], "REUSE")
        self.assertEqual(result["appStoreVersionId"], "ed-100")
        self.assertEqual(calls, [])
        self.assertNotIn("must be strictly greater", stderr)
        self.assertIn("decision=REUSE", stderr)

    def test_target_matches_terminal_at_target_exits_2(self):
        """Same versionString already shipped (READY_FOR_SALE) at exactly
        target -> SystemExit(2)."""
        terminal = _v("1.0.0", "READY_FOR_SALE", vid="rfs-100")
        stderr = _assert_decide_exits(
            self, "1.0.0", [terminal],
            ground_truth="1.0.0", combined_floor="1.0.0",
        )
        self.assertIn("1.0.0", stderr)
        self.assertIn("READY_FOR_SALE", stderr)
        self.assertIn("fresh marketing version", stderr)
        _assert_mentions_build_setting_source(self, stderr)

    def test_target_matches_editable_and_terminal_exits_2(self):
        """Issue 8 regression guard: ASC normally enforces a single row
        per versionString, but if both editable PFS and terminal RFS
        co-exist at target, REUSE'ing the editable would silently
        proceed past a row the user actually shipped."""
        editable = _v("1.0.0", "PREPARE_FOR_SUBMISSION", vid="ed-100")
        terminal = _v("1.0.0", "READY_FOR_SALE", vid="rfs-100")
        stderr = _assert_decide_exits(
            self, "1.0.0", [editable, terminal],
            ground_truth="1.0.0", combined_floor="1.0.0",
        )
        self.assertIn("1.0.0", stderr)
        self.assertIn("READY_FOR_SALE", stderr)
        self.assertIn("editable row at the same versionString", stderr)
        self.assertIn("fresh marketing version", stderr)
        _assert_mentions_build_setting_source(self, stderr)

    def test_target_matches_waiting_for_review_is_testflight_only(self):
        """A version Apple is reviewing locks the whole App Store slot:
        REUSE would attach a build to the row under review and CREATE
        would 409. Neither a human wait nor a bump is asked for any
        more -- the build goes to TestFlight under the locked version
        (TESTFLIGHT_ONLY) with no App Store version id, and no
        CREATE/REUSE seam is touched."""
        in_review = _v("1.0.0", "WAITING_FOR_REVIEW", vid="ed-wfr-100")
        result, stderr, calls = _run_decide_for_version(
            "1.0.0", versions=[in_review],
            ground_truth="1.0.0", combined_floor="1.0.0",
        )
        self.assertEqual(result["decision"], "TESTFLIGHT_ONLY")
        self.assertEqual(result["versionString"], "1.0.0")
        self.assertEqual(result["appStoreVersionId"], "")
        self.assertEqual(result["state"], "WAITING_FOR_REVIEW")
        self.assertEqual(result["storeVersionLocked"], "1.0.0")
        self.assertEqual(calls, [])
        self.assertIn("decision=TESTFLIGHT_ONLY", stderr)
        self.assertIn("ed-wfr-100", stderr)

    def test_target_matches_in_review_is_testflight_only(self):
        """IN_REVIEW locks the slot exactly as WAITING_FOR_REVIEW does."""
        in_review = _v("1.0.0", "IN_REVIEW", vid="ed-ir-100")
        result, stderr, calls = _run_decide_for_version(
            "1.0.0", versions=[in_review],
            ground_truth="1.0.0", combined_floor="1.0.0",
        )
        self.assertEqual(result["decision"], "TESTFLIGHT_ONLY")
        self.assertEqual(result["state"], "IN_REVIEW")
        self.assertEqual(calls, [])
        self.assertIn("decision=TESTFLIGHT_ONLY", stderr)

    def test_target_matches_developer_rejected_reuses(self):
        """Issue 13: DEVELOPER_REJECTED is in REUSABLE_STATES — the
        developer explicitly took the row out of review, so REUSE is
        the correct steady-state behaviour."""
        rejected = _v("1.0.0", "DEVELOPER_REJECTED", vid="ed-dr-100")
        result, stderr, calls = _run_decide_for_version(
            "1.0.0", versions=[rejected], ground_truth=None,
        )
        self.assertEqual(result["decision"], "REUSE")
        self.assertEqual(result["appStoreVersionId"], "ed-dr-100")
        self.assertEqual(calls, [])
        self.assertIn("decision=REUSE", stderr)


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