#!/usr/bin/env python3
"""
Tests for asc_build_history.fetch_builds_prerelease_versions, the
/v1/builds?include=preReleaseVersion path that powers the
buildsViaPreRelease component of the combined floor.

This module owns one large class, ``BuildsPreReleaseRelationshipTests``,
which was lifted out of ``test_asc_build_history.py`` purely to keep
that file under the 400-line cap. Both modules share helper imports
from ``tests_common``.
"""

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, _v  # noqa: E402


def _build_data_entry(build_id: str, build_version: str, pr_id: str) -> dict:
    """One /v1/builds.data entry with a preReleaseVersion relationship."""
    return {
        "id": build_id,
        "type": "builds",
        "attributes": {"version": build_version},
        "relationships": {
            "preReleaseVersion": {
                "data": {"type": "preReleaseVersions", "id": pr_id},
            },
        },
    }


def _prerelease_included(
    pr_id: str, pr_version: str, *, platform: str | None = "IOS",
) -> dict:
    """One /v1/builds.included entry describing a preReleaseVersion."""
    attrs: dict = {"version": pr_version}
    if platform is not None:
        attrs["platform"] = platform
    return {
        "id": pr_id,
        "type": "preReleaseVersions",
        "attributes": attrs,
    }


def _build_with_prerelease(
    build_id: str, build_version: str,
    pr_id: str, pr_version: str, *, platform: str | None = "IOS",
) -> dict:
    """Build the /v1/builds payload pairing one build with one prerelease
    relationship — the canonical fixture shape these tests exercise."""
    return {
        "data": [_build_data_entry(build_id, build_version, pr_id)],
        "included": [
            _prerelease_included(pr_id, pr_version, platform=platform),
        ],
        "links": {},
    }


def _capture_build_history_request(payload: dict):
    """Patch asc_build_history.request to return `payload`. Returns
    (seen_paths, seen_params, ctx) where ctx is the unittest.mock patch."""
    seen_paths: list[str] = []
    seen_params: list[dict | None] = []

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

    return seen_paths, seen_params, mock.patch.object(
        asc_build_history, "request", side_effect=fake_request,
    )


class BuildsPreReleaseRelationshipTests(unittest.TestCase):
    """/v1/builds?include=preReleaseVersion reveals preReleaseVersions
    that the direct /preReleaseVersions query might miss."""

    def test_buildsViaPreRelease_included_in_floor(self):
        payload = _build_with_prerelease(
            "build-1", "42", "pr-ref-106", "1.0.6",
        )
        seen_paths, seen_params, patch_ctx = (
            _capture_build_history_request(payload)
        )

        buf = io.StringIO()
        with patch_ctx, redirect_stderr(buf):
            versions = asc_build_history.fetch_builds_prerelease_versions(
                "111", "tok",
            )

        self.assertEqual(versions, ["1.0.6"])
        self.assertEqual(seen_paths[0], "/builds")
        self.assertEqual(seen_params[0].get("filter[app]"), "111")
        self.assertEqual(seen_params[0].get("include"), "preReleaseVersion")

        with mock.patch.object(
                asc_build_history, "fetch_prerelease_versions",
                return_value=[]), \
                mock.patch.object(
                    asc_build_history, "fetch_builds_prerelease_versions",
                    return_value=["1.0.6"]):
            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.6")
        self.assertEqual(per_source.get("buildsViaPreRelease"), "1.0.6")

    def test_buildsViaPreRelease_uses_top_level_builds_endpoint(self):
        payload = _build_with_prerelease("b1", "1", "pr1", "1.0.9")
        seen_paths, seen_params, patch_ctx = (
            _capture_build_history_request(payload)
        )

        with patch_ctx:
            versions = asc_build_history.fetch_builds_prerelease_versions(
                "111", "tok",
            )

        self.assertEqual(versions, ["1.0.9"])
        self.assertEqual(seen_paths[0], "/builds")
        self.assertNotEqual(seen_paths[0], "/apps/111/builds")
        params = seen_params[0] or {}
        self.assertEqual(params.get("filter[app]"), "111")
        self.assertEqual(params.get("include"), "preReleaseVersion")

    def test_buildsViaPreRelease_excludes_non_ios_platform(self):
        payload = {
            "data": [
                _build_data_entry("build-mac", "1", "pr-ref-mac"),
                _build_data_entry("build-null", "2", "pr-ref-null"),
            ],
            "included": [
                _prerelease_included(
                    "pr-ref-mac", "9.9.9", platform="MAC_OS"),
                _prerelease_included(
                    "pr-ref-null", "1.0.8", platform=None),
            ],
            "links": {},
        }
        _, _, patch_ctx = _capture_build_history_request(payload)

        with patch_ctx:
            versions = asc_build_history.fetch_builds_prerelease_versions(
                "111", "tok",
            )

        self.assertEqual(versions, ["1.0.8"])

    def test_buildsViaPreRelease_handles_missing_relationship(self):
        payload = {
            "data": [
                {
                    "id": "build-orphan",
                    "type": "builds",
                    "attributes": {"version": "1"},
                    "relationships": {},
                },
            ],
            "included": [],
            "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_builds_prerelease_versions(
                "111", "tok",
            )

        self.assertEqual(versions, [])

    def test_buildsViaPreRelease_paginated(self):
        page1 = _build_with_prerelease("b1", "1", "pr1", "1.0.5")
        page1["links"] = {
            "next": "https://api.appstoreconnect.apple.com/v1/"
                    "builds?cursor=PAGE2",
        }
        page2 = _build_with_prerelease("b2", "2", "pr2", "1.0.6")
        responses = [_FakeResponse(page1), _FakeResponse(page2)]
        seen_paths: list[str] = []

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

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

        self.assertEqual(sorted(versions), ["1.0.5", "1.0.6"])
        self.assertEqual(len(responses), 0)
        self.assertEqual(seen_paths[0], "/builds")


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