"""Real curl retries a transient origin refusal without publishing partial bytes."""
import hashlib
import json
import os
from pathlib import Path
import subprocess
import tempfile
import unittest

import flutter_manifest

from flutter_manifest_fixture import MANIFEST, tls_origin

ROOT = Path(__file__).parent
ARCHIVE = bytes(range(256)) * 512


class OriginRetryTests(unittest.TestCase):
    def setUp(self):
        directory = tempfile.TemporaryDirectory()
        self.addCleanup(directory.cleanup)
        self.root = Path(directory.name)

    def invoke(self, server, cert, *, archive=False):
        env = {**os.environ, "HOME": str(self.root), "RUNNER_TEMP": str(self.root),
               "BASH_ENV": str(ROOT / "flutter_manifest_env.sh"),
               "GOWALK_FLUTTER_MANIFEST_HELPER": str(ROOT / "flutter_manifest.py"),
               "CURL_CA_BUNDLE": str(cert),
               "VERSION_MANIFEST": json.dumps({"sha256": hashlib.sha256(ARCHIVE).hexdigest()})}
        for key in ("SSL_CERT_FILE", "SSL_CERT_DIR", "HTTPS_PROXY", "https_proxy",
                    "HTTP_PROXY", "http_proxy", "ALL_PROXY", "all_proxy"):
            env.pop(key, None)
        # The official setup invokes the manifest GET with --silent and the archive GET without it;
        # flutter_manifest only recognises those exact argument vectors.
        path = ("/flutter_infra_release/releases/stable/linux/flutter_linux_3.44.0-stable.tar.xz"
                if archive else "/flutter_infra_release/releases/releases_linux.json")
        silent = "" if archive else "--silent "
        return subprocess.run(["bash", "-c", f'curl {silent}--connect-timeout 15 --retry 5 "$1"', "bash",
                               server.url(path)], env=env, capture_output=True, timeout=60)

    def test_transient_origin_refusal_recovers_on_the_next_attempt(self):
        for status in ("500", "502", "503", "504", "429", "408"):
            with self.subTest(status=status), tls_origin(self.root, [f"origin_{status}", "ok"],
                                                         MANIFEST) as (server, cert):
                result = self.invoke(server, cert)
                self.assertEqual((result.returncode, result.stdout), (0, MANIFEST), result.stderr)
                self.assertEqual(server.connects, 2)
                self.assertIn(b"flutter_manifest_recovered attempts=2", result.stderr)

    def test_repeated_origin_refusal_exhausts_the_window_without_output(self):
        limit = flutter_manifest.MAX_ATTEMPTS["manifest"]
        with tls_origin(self.root, ["origin_503"] * limit, MANIFEST) as (server, cert):
            result = self.invoke(server, cert)
            self.assertNotEqual(result.returncode, 0)
            self.assertEqual((result.stdout, server.connects), (b"", limit))
            self.assertIn(f"attempts={limit}".encode(), result.stderr)

    def test_origin_refusal_preserves_a_partial_archive_for_a_verified_range_resume(self):
        with tls_origin(self.root, ["partial", "origin_503", "range"], ARCHIVE) as (server, cert):
            result = self.invoke(server, cert, archive=True)
            self.assertEqual((result.returncode, result.stdout), (0, ARCHIVE), result.stderr)
            self.assertEqual(server.ranges, ["", "bytes=31-", "bytes=31-"])


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