"""Completed-file recovery keeps byte identity without inventing optional provider attributes."""
import base64
from contextlib import redirect_stdout
import hashlib
import io
from pathlib import Path
import sys
import tempfile
import unittest
from unittest import mock

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


class CompletedUploadTests(unittest.TestCase):
    def test_provider_exit_emits_typed_failure_without_exception_content(self):
        output = io.StringIO()
        with tempfile.TemporaryDirectory() as temporary, redirect_stdout(output), \
                mock.patch.object(sys, "argv", ["upload_build.py", "app.ipa", "--report", temporary + "/receipt.json"]), \
                mock.patch.object(upload_build, "deliver", side_effect=SystemExit("private-provider-detail")):
            with self.assertRaisesRegex(SystemExit, "retain its receipt"):
                upload_build.main()
        self.assertIn("::error title=apple_build_upload_failed::", output.getvalue())
        self.assertNotIn("private-provider-detail", output.getvalue())

    def test_provider_completion_without_optional_checksum_reuses_exact_file(self):
        info = {"fileName": "source-sha256.ipa", "fileSize": 5}
        file = {"attributes": {**info, "assetDeliveryState": {"state": "COMPLETE"}}}
        with mock.patch.object(upload_build, "api") as api, mock.patch.object(upload_build, "transfer") as transfer:
            upload_build.transfer_all(Path("not-read"), file, info)
            api.assert_not_called()
            transfer.assert_not_called()
            file["attributes"]["fileName"] = "another-source.ipa"
            with self.assertRaisesRegex(ValueError, "identity"):
                upload_build.transfer_all(Path("not-read"), file, info)

    def test_present_provider_checksums_are_verified_against_actual_bytes(self):
        with tempfile.TemporaryDirectory() as temporary:
            ipa = Path(temporary) / "app.ipa"
            ipa.write_bytes(b"the uploaded file")
            info = {"fileName": "source-sha256.ipa", "fileSize": ipa.stat().st_size}
            for algorithm, digest in [("SHA_256", hashlib.sha256(ipa.read_bytes())),
                                      ("MD5", hashlib.md5(ipa.read_bytes()))]:
                for value in [digest.hexdigest(), base64.b64encode(digest.digest()).decode()]:
                    with self.subTest(algorithm=algorithm, encoding=value):
                        attributes = {**info, "assetDeliveryState": {"state": "COMPLETE"},
                                      "sourceFileChecksums": {"file": {"hash": value, "algorithm": algorithm}}}
                        upload_build.transfer_all(ipa, {"attributes": attributes}, info)
                        attributes["sourceFileChecksums"]["file"]["hash"] = "different-bytes"
                        with self.assertRaisesRegex(ValueError, "checksum"):
                            upload_build.transfer_all(ipa, {"attributes": attributes}, info)

    def test_commit_uses_documented_uploaded_attribute_after_all_bytes_finish(self):
        # Apple's WWDC25 BuildUpload sample sends uploaded=true. The observed IPA
        # endpoint rejected the optional SHA_256 sourceFileChecksums object with409.
        parts = [{"offset": 0, "length": 2}, {"offset": 2, "length": 3}]
        done = []
        def provider(method, path, body):
            self.assertEqual(sorted(done), [0, 2])
            attributes = body["data"]["attributes"]
            if "sourceFileChecksums" in attributes:
                raise SystemExit("409 ENTITY_ERROR.ATTRIBUTE.INVALID")
            self.assertTrue(attributes["uploaded"])
            return {"data": {"id": "existing-file"}}
        with mock.patch.object(upload_build, "api", side_effect=provider) as api, \
                mock.patch.object(upload_build, "transfer", side_effect=lambda _path, op: done.append(op["offset"])):
            upload_build.transfer_all(Path("not-read"), {"id": "existing-file", "attributes": {
                "uploadOperations": parts}}, {"fileSize": 5, "sha256": "local-receipt-digest"})
            self.assertEqual(api.call_count, 1)


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