"""Edit inserts retry only transport evidence that excludes an origin request."""
import io
from pathlib import Path
import ssl
import sys
import unittest
from unittest import mock

import requests
from urllib3 import exceptions as errors

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


def proxy_failure(cause=None):
    cause = cause or OSError("Tunnel connection failed: 502 private proxy refusal")
    reason = errors.ProxyError("private proxy", cause)
    return requests.exceptions.ProxyError(errors.MaxRetryError(None, "private URL", reason))


class ConnectRetryTests(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_proven_connection_failures_recover_with_one_successful_insert(self):
        connection = errors.NewConnectionError(None, "private connect failure")
        direct = requests.exceptions.ConnectionError(errors.MaxRetryError(None, "private URL", connection))
        for error in (requests.exceptions.ConnectTimeout("private"), proxy_failure(),
                      proxy_failure(connection), direct):
            with self.subTest(kind=type(error).__name__):
                response = mock.Mock(status_code=200)
                operation = mock.Mock(side_effect=[error, response])
                self.assertIs(transport.call("edit_create", operation, retry_connect=True, accepted=(200,)), response)
                self.assertEqual(operation.call_count, 2)
                self.assertNotIn("private", self.output.getvalue())

    def test_ambiguous_errors_never_authorize_an_insert_retry(self):
        for error in (requests.exceptions.ReadTimeout("private"), requests.exceptions.ProxyError("private"),
                      requests.exceptions.ConnectionError(errors.ProtocolError("private")),
                      proxy_failure(OSError("unknown private refusal")),
                      proxy_failure(OSError("Tunnel connection failed: 407 private authentication")),
                      proxy_failure(errors.ProtocolError("Tunnel connection failed: 502 private"))):
            with self.subTest(kind=type(error).__name__):
                operation = mock.Mock(side_effect=error)
                with self.assertRaises(SystemExit):
                    transport.call("edit_create", operation, retry_connect=True)
                operation.assert_called_once()
        self.sleep.assert_not_called()

    def test_proxy_wrapped_tls_failure_remains_terminal(self):
        error = proxy_failure(errors.SSLError(ssl.SSLCertVerificationError("private certificate")))
        operation = mock.Mock(side_effect=error)
        with self.assertRaises(SystemExit):
            transport.call("edit_create", operation, retry_connect=True)
        operation.assert_called_once()
        self.sleep.assert_not_called()
        self.assertNotIn("private", self.output.getvalue())

    def test_origin_response_never_uses_connection_retry_permission(self):
        operation = mock.Mock(return_value=mock.Mock(status_code=503))
        with self.assertRaises(SystemExit) as caught:
            transport.call("edit_create", operation, retry_connect=True, accepted=(200, 404))
        operation.assert_called_once()
        self.sleep.assert_not_called()
        self.assertIn('"status": 503', str(caught.exception))

    def test_lost_response_after_a_connection_retry_stops_at_that_attempt(self):
        operation = mock.Mock(side_effect=[proxy_failure(), requests.exceptions.ReadTimeout("private")])
        with self.assertRaises(SystemExit) as caught:
            transport.call("edit_create", operation, retry_connect=True)
        self.assertEqual(operation.call_count, 2)
        self.assertIn('"attempts": 2', str(caught.exception))
        self.sleep.assert_called_once_with(1)

    def test_failed_connections_are_bounded_and_keep_safe_evidence(self):
        operation = mock.Mock(side_effect=proxy_failure())
        with self.assertRaises(SystemExit) as caught:
            transport.call("edit_create", operation, retry_connect=True)
        self.assertEqual(operation.call_count, 3)
        self.assertEqual(self.sleep.call_args_list, [mock.call(1), mock.call(2)])
        self.assertIn('"attempts": 3', str(caught.exception))
        self.assertIn('"code": "proxy_connection_failed"', str(caught.exception))
        self.assertNotIn("private", str(caught.exception) + self.output.getvalue())

    def test_other_writes_have_no_implicit_retry_permission(self):
        operation = mock.Mock(side_effect=proxy_failure())
        with self.assertRaises(SystemExit):
            transport.call("another_write", operation)
        operation.assert_called_once()
        self.sleep.assert_not_called()


class PreflightConnectWiringTests(unittest.TestCase):
    def test_real_preflight_recovers_connection_before_one_known_edit_cleanup(self):
        import play_preflight as preflight

        client = mock.Mock()
        client.post.side_effect = [proxy_failure(), mock.Mock(status_code=200, text='{"id":"known-edit"}')]
        client.delete.return_value = mock.Mock(status_code=204)
        credentials = mock.Mock(token="private token")
        with mock.patch.object(preflight.service_account.Credentials, "from_service_account_file",
                               return_value=credentials), mock.patch.object(preflight, "write_ready") as ready, \
                mock.patch.object(transport.time, "sleep"), mock.patch("sys.stdout", new_callable=io.StringIO):
            preflight.check_ready(client, "com.example.app", Path("account.json"))
        self.assertEqual(client.post.call_count, 2)
        self.assertEqual(client.post.call_args_list[0], client.post.call_args_list[1])
        client.delete.assert_called_once()
        self.assertTrue(client.delete.call_args.args[0].endswith("/known-edit"))
        ready.assert_called_once_with(True)


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