#!/usr/bin/env python3
"""
Tests for asc_build_history.fetch_prerelease_versions and the
combined-floor wiring that consumes both /preReleaseVersions and
/builds?include=preReleaseVersion.

Why these live in their own module: they exercise asc_build_history
directly (not via mmv seams), and they're large enough that bundling
them with the other floor-check tests pushed test_mmv_floor_check.py
over the 400-line cap. The /builds?include=preReleaseVersion path is
covered separately in ``test_asc_builds_prerelease.py``.
"""

from __future__ import annotations

import io
import sys
import unittest
from contextlib import redirect_stderr
from pathlib import Path
from unittest import mock

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

import manage_marketing_version as mmv  # noqa: E402
import asc_version_fetch  # noqa: E402
import asc_build_history  # noqa: E402

from tests_common import _FakeResponse, _mk_prerelease_entry, _v  # noqa: E402


class PrereleaseFetchShapeTests(unittest.TestCase):
    """The /preReleaseVersions fetcher must use the correct query shape and
    paginate."""

    def test_prerelease_fetch_client_side_platform_filter(self):
        seen_params: list[dict | None] = []
        payload = {
            "data": [
                {
                    "id": "pr-ios",
                    "attributes": {"version": "1.0.1", "platform": "IOS"},
                },
                {
                    "id": "pr-mac",
                    "attributes": {"version": "1.0.2", "platform": "MAC_OS"},
                },
                {
                    "id": "pr-null",
                    "attributes": {"version": "1.0.3"},
                },
            ],
            "links": {},
        }

        def fake_request(method, path, token, *, params=None, **_kw):
            seen_params.append(params)
            return _FakeResponse(payload)

        buf = io.StringIO()
        with mock.patch.object(
                asc_build_history, "request", side_effect=fake_request), \
                redirect_stderr(buf):
            got = asc_build_history.fetch_prerelease_versions("111", "tok")

        self.assertTrue(seen_params)
        first = seen_params[0] or {}
        self.assertNotIn("filter[platform]", first)
        self.assertEqual(first.get("limit"), 200)
        self.assertEqual(sorted(got), ["1.0.1", "1.0.3"])
        stderr = buf.getvalue()
        self.assertIn("version=1.0.1", stderr)
        self.assertIn("platform=IOS", stderr)
        self.assertIn("pr-mac", stderr)
        self.assertIn("MAC_OS", stderr)
        self.assertIn("not-ios", stderr)
        self.assertIn("pr-null", stderr)

    def test_prerelease_fetch_combined_floor_only_counts_ios(self):
        payload = {
            "data": [
                {
                    "id": "pr-ios",
                    "attributes": {"version": "1.0.4", "platform": "IOS"},
                },
                {
                    "id": "pr-mac-high",
                    "attributes": {"version": "9.9.9", "platform": "MAC_OS"},
                },
            ],
            "links": {},
        }

        def fake_request(method, path, token, *, params=None, **_kw):
            return _FakeResponse(payload)

        with mock.patch.object(
                asc_build_history, "request", side_effect=fake_request):
            floor, _ = mmv.get_combined_floor(
                "111", "tok", appstore_versions=[],
            )
        self.assertEqual(floor, "1.0.4")

    def test_prerelease_fetch_paginated(self):
        seen_params: list[dict | None] = []
        page1 = {
            "data": [
                _mk_prerelease_entry("1.0.1", pid="pr-101"),
                _mk_prerelease_entry("1.0.2", pid="pr-102"),
            ],
            "links": {
                "next": "https://api.appstoreconnect.apple.com/v1/apps/111/"
                        "preReleaseVersions?cursor=PAGE2",
            },
        }
        page2 = {
            "data": [_mk_prerelease_entry("1.0.4", pid="pr-104")],
            "links": {},
        }
        responses = [_FakeResponse(page1), _FakeResponse(page2)]

        def fake_request(method, path, token, *, params=None, **_kw):
            seen_params.append(params)
            return responses.pop(0)

        with mock.patch.object(
                asc_build_history, "request", side_effect=fake_request):
            got = asc_build_history.fetch_prerelease_versions("111", "tok")

        self.assertEqual(sorted(got), ["1.0.1", "1.0.2", "1.0.4"])
        self.assertEqual(len(responses), 0)
        for p in seen_params:
            if p is None:
                continue
            self.assertNotIn("filter[platform]", p)


class PlatformNullDefensiveInclusionTests(unittest.TestCase):
    """Null/missing platform must be INCLUDED defensively."""

    def test_platform_null_record_included_defensively(self):
        payload = {
            "data": [
                {
                    "id": "pr-null",
                    "attributes": {"version": "1.0.9"},
                },
            ],
            "links": {},
        }

        def fake_request(method, path, token, *, params=None, **_kw):
            return _FakeResponse(payload)

        buf = io.StringIO()
        with mock.patch.object(
                asc_build_history, "request", side_effect=fake_request), \
                redirect_stderr(buf):
            versions = asc_build_history.fetch_prerelease_versions(
                "111", "tok",
            )

        self.assertEqual(versions, ["1.0.9"])
        stderr = buf.getvalue()
        self.assertIn("pr-null", stderr)
        self.assertIn("null", stderr.lower())

    def test_platform_explicit_non_ios_still_excluded(self):
        payload = {
            "data": [
                {
                    "id": "pr-mac",
                    "attributes": {"version": "9.9.9", "platform": "MAC_OS"},
                },
                {
                    "id": "pr-tv",
                    "attributes": {"version": "8.8.8", "platform": "TV_OS"},
                },
                {
                    "id": "pr-vision",
                    "attributes": {"version": "7.7.7", "platform": "VISION_OS"},
                },
            ],
            "links": {},
        }

        def fake_request(method, path, token, *, params=None, **_kw):
            return _FakeResponse(payload)

        with mock.patch.object(
                asc_build_history, "request", side_effect=fake_request):
            versions = asc_build_history.fetch_prerelease_versions(
                "111", "tok",
            )

        self.assertEqual(versions, [])


class CombinedFloorBuildsViaSourceTests(unittest.TestCase):
    """Combined floor per_source must include all three sources."""

    def test_per_source_breakdown_includes_buildsViaPreRelease(self):
        with mock.patch.object(
                asc_build_history, "fetch_prerelease_versions",
                return_value=["1.0.4"]), \
                mock.patch.object(
                    asc_build_history, "fetch_builds_prerelease_versions",
                    return_value=["1.0.7"]):
            floor, per_source = asc_version_fetch.get_combined_floor(
                "111", "tok", mmv.semver_tuple,
                appstore_versions=[_v("1.0", "READY_FOR_SALE")],
            )

        self.assertEqual(floor, "1.0.7")
        self.assertEqual(per_source.get("appStoreVersions"), "1.0")
        self.assertEqual(per_source.get("preReleaseVersions"), "1.0.4")
        self.assertEqual(per_source.get("buildsViaPreRelease"), "1.0.7")


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