"""Preflight retries safe operations and never duplicates an ambiguous edit insert."""
import io
import json
from pathlib import Path
import sys
import unittest
from unittest import mock

import requests

sys.path.insert(0, str(Path(__file__).resolve().parent))
import play_preflight as preflight
import play_preflight_transport as transport


class PlayPreflightTests(unittest.TestCase):
    def setUp(self):
        self.client = mock.Mock()
        self.client.post.return_value = mock.Mock(status_code=200, text='{"id":"edit-123"}')
        self.client.delete.return_value = mock.Mock(status_code=204)
        self.credentials = mock.Mock(token="private-token")
        self.factory = self.enterContext(mock.patch.object(preflight.service_account.Credentials,
                                                          "from_service_account_file"))
        self.factory.return_value = self.credentials
        self.ready = self.enterContext(mock.patch.object(preflight, "write_ready"))
        self.sleep = self.enterContext(mock.patch.object(transport.time, "sleep"))
        self.output = self.enterContext(mock.patch("sys.stdout", new_callable=io.StringIO))

    def check(self):
        return preflight.check_ready(self.client, "com.example.app", Path("account.json"))

    def test_oauth_transport_recovers_before_one_edit_is_created(self):
        self.credentials.refresh.side_effect = [requests.exceptions.ProxyError("private-proxy"), None]
        self.check()
        self.assertEqual(self.credentials.refresh.call_count, 2)
        self.client.post.assert_called_once()
        self.client.delete.assert_called_once()
        self.ready.assert_called_once_with(True)
        request = self.credentials.refresh.call_args.args[0]
        self.assertIs(request.func.session, self.client)
        self.assertEqual(request.keywords, {"timeout": 30})
        self.assertNotIn("private", self.output.getvalue())

    def test_insert_timeout_is_not_replayed_or_reported_ready(self):
        self.client.post.side_effect = requests.exceptions.ReadTimeout("private-token")
        with self.assertRaises(SystemExit) as caught:
            self.check()
        self.assertIn('"phase": "edit_create"', str(caught.exception))
        self.assertIn('"code": "timed_out"', str(caught.exception))
        self.assertNotIn("private", str(caught.exception))
        self.client.post.assert_called_once()
        self.client.delete.assert_not_called()
        self.ready.assert_not_called()
        self.sleep.assert_not_called()

    def test_delete_timeout_then_absence_closes_only_the_known_edit(self):
        self.client.delete.side_effect = [requests.exceptions.ReadTimeout(), mock.Mock(status_code=404)]
        self.check()
        self.client.post.assert_called_once()
        self.assertEqual(self.client.delete.call_count, 2)
        self.assertEqual(self.client.delete.call_args_list[0], self.client.delete.call_args_list[1])
        self.assertTrue(self.client.delete.call_args.args[0].endswith("/edit-123"))
        self.ready.assert_called_once_with(True)

    def test_cleanup_exhaustion_identifies_edit_without_ready_or_duplicate_insert(self):
        self.client.delete.side_effect = requests.exceptions.ConnectionError("private-proxy")
        with self.assertRaises(SystemExit) as caught:
            self.check()
        evidence = json.loads(str(caught.exception).split(": ", 1)[1])
        self.assertEqual(evidence["edit_id"], "edit-123")
        self.assertTrue(evidence["cleanup_required"])
        self.assertEqual(evidence["attempts"], 3)
        self.assertNotIn("private", str(caught.exception))
        self.client.post.assert_called_once()
        self.assertEqual(self.client.delete.call_count, 3)
        self.ready.assert_not_called()

    def test_provider_refusal_hides_untrusted_body_and_is_not_retried(self):
        self.client.post.return_value = mock.Mock(status_code=403, text="private-access-token")
        with self.assertRaises(SystemExit) as caught:
            self.check()
        self.assertIn('"status": 403', str(caught.exception))
        self.assertNotIn("private", str(caught.exception))
        self.client.post.assert_called_once()
        self.ready.assert_not_called()

    def test_unregistered_package_keeps_first_release_path(self):
        self.client.post.return_value = mock.Mock(status_code=404)
        self.check()
        self.ready.assert_called_once_with(False)
        self.client.delete.assert_not_called()

    def test_opaque_edit_identifier_is_encoded_in_cleanup_path(self):
        self.client.post.return_value.text = '{"id":"opaque/id?x=y"}'
        self.check()
        self.assertTrue(self.client.delete.call_args.args[0].endswith("/opaque%2Fid%3Fx%3Dy"))


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