"""Classified preflight diagnostics never include raw provider/credential exceptions."""
import io
from pathlib import Path
import sys
import unittest
from unittest import mock

from google.auth.exceptions import RefreshError, TransportError
import requests

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


class PreflightTransportTests(unittest.TestCase):
    def setUp(self):
        self.sleep = self.enterContext(mock.patch.object(transport.time, "sleep"))
        self.output = self.enterContext(mock.patch("sys.stdout", new_callable=io.StringIO))

    def test_wrapped_oauth_transport_error_classification_does_not_read_raw_text(self):
        for error, expected in ((requests.exceptions.ProxyError("private"), "proxy_connection_failed"),
                                (requests.exceptions.SSLError("private"), "tls_failed"),
                                (requests.exceptions.ReadTimeout("private"), "timed_out")):
            wrapped = TransportError(error)
            self.assertEqual(transport.transport_code(wrapped), expected)

    def test_safe_operation_has_bounded_retry_and_sanitized_terminal_error(self):
        operation = mock.Mock(side_effect=TransportError(requests.exceptions.ProxyError("private")))
        with self.assertRaises(SystemExit) as caught:
            transport.call("oauth_refresh", operation, retry=True)
        self.assertEqual(operation.call_count, 3)
        self.assertEqual(self.sleep.call_args_list, [mock.call(1), mock.call(2)])
        self.assertIn('"code": "proxy_connection_failed"', str(caught.exception))
        self.assertNotIn("private", str(caught.exception) + self.output.getvalue())

    def test_oauth_provider_error_is_sanitized_without_an_extra_retry(self):
        operation = mock.Mock(side_effect=RefreshError("private-provider-body"))
        with self.assertRaises(SystemExit) as caught:
            transport.call("oauth_refresh", operation, retry=True)
        operation.assert_called_once()
        self.assertIn("oauth_refresh_failed", str(caught.exception))
        self.assertNotIn("private", str(caught.exception))

    def test_cleanup_retries_server_failure_then_returns_the_success(self):
        good = mock.Mock(status_code=204)
        operation = mock.Mock(side_effect=[mock.Mock(status_code=503), good])
        self.assertIs(transport.call("edit_cleanup", operation, retry=True, edit_id="123"), good)
        self.assertEqual(operation.call_count, 2)

    def test_ambiguous_server_error_on_insert_is_never_retried(self):
        response = mock.Mock(status_code=503)
        operation = mock.Mock(return_value=response)
        self.assertIs(transport.call("edit_create", operation), response)
        operation.assert_called_once()
        self.sleep.assert_not_called()

    def test_cleanup_server_failure_exhaustion_preserves_classified_evidence(self):
        operation = mock.Mock(return_value=mock.Mock(status_code=503))
        with self.assertRaises(SystemExit) as caught:
            transport.call("edit_cleanup", operation, retry=True, edit_id="123")
        self.assertIn('"code": "provider_unavailable"', str(caught.exception))
        self.assertIn('"status": 503', str(caught.exception))
        self.assertIn('"edit_id": "123"', str(caught.exception))
        self.assertEqual(operation.call_count, 3)

    def test_a_transient_status_is_unavailable_even_where_the_phase_cannot_replay(self):
        """`edit_create` must not repeat an ambiguous insert, but naming its 503 a
        refusal told the next session to go and fix a configuration Google never
        objected to. The status decides the word; the retry policy is unchanged."""
        operation = mock.Mock(return_value=mock.Mock(status_code=503))
        with self.assertRaises(SystemExit) as caught:
            transport.call("edit_create", operation, accepted=(200, 404), retry_connect=True)
        self.assertIn('"code": "provider_unavailable"', str(caught.exception))
        self.assertIn('"status": 503', str(caught.exception))
        self.assertIn('"attempts": 1', str(caught.exception))
        operation.assert_called_once()
        self.sleep.assert_not_called()

    def test_a_real_refusal_keeps_its_own_word(self):
        operation = mock.Mock(return_value=mock.Mock(status_code=403))
        with self.assertRaises(SystemExit) as caught:
            transport.call("edit_create", operation, accepted=(200, 404), retry_connect=True)
        self.assertIn('"code": "provider_refused"', str(caught.exception))
        self.assertIn('"status": 403', str(caught.exception))

    def test_refusal_after_retry_records_the_actual_attempt_in_a_safe_annotation(self):
        operation = mock.Mock(side_effect=[mock.Mock(status_code=503), mock.Mock(status_code=403)])
        with self.assertRaises(SystemExit) as caught:
            transport.call("edit_cleanup", operation, retry=True, edit_id="123", accepted=(200, 204, 404))
        self.assertIn('"attempts": 2', str(caught.exception))
        self.assertIn("::error title=play_preflight_failed::", self.output.getvalue())
        self.assertIn('"cleanup_required": true', self.output.getvalue())


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