import asyncio
import multiprocessing

# Third Party
import aiohttp
import aiohttp.web
from aiohttp.web import StreamResponse
import pytest

# aiobotocore
from tests.moto_server import host, MotoService, get_free_tcp_port


_proxy_bypass = {
  "http": None,
  "https": None,
}


# This runs in a subprocess for a variety of reasons
# 1) early versions of python 3.5 did not correctly set one thread per run loop
# 2) aiohttp uses get_event_loop instead of using the passed in run loop
# 3) aiohttp shutdown can be hairy
class AIOServer(multiprocessing.Process):
    """
    This is a mock AWS service which will 5 seconds before returning
    a response to test socket timeouts.
    """
    def __init__(self):
        super().__init__(target=self._run)
        self._loop = None
        self._port = get_free_tcp_port(True)
        self.endpoint_url = f'http://{host}:{self._port}'
        self.daemon = True  # die when parent dies

    def _run(self):
        asyncio.set_event_loop(asyncio.new_event_loop())
        app = aiohttp.web.Application()
        app.router.add_route('*', '/ok', self.ok)
        app.router.add_route('*', '/{anything:.*}', self.stream_handler)

        try:
            aiohttp.web.run_app(app, host=host, port=self._port,
                                handle_signals=False)
        except BaseException:
            pytest.fail('unable to start and connect to aiohttp server')
            raise

    async def __aenter__(self):
        self.start()
        await self._wait_until_up()
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        try:
            self.terminate()
        except BaseException:
            pytest.fail("Unable to shut down server")
            raise

    @staticmethod
    async def ok(request):
        return aiohttp.web.Response()

    async def stream_handler(self, request):
        # Without the Content-Type, most (all?) browsers will not render
        # partially downloaded content. Note, the response type is
        # StreamResponse not Response.
        resp = StreamResponse(status=200, reason='OK',
                              headers={'Content-Type': 'text/html'})

        await resp.prepare(request)
        await asyncio.sleep(5)
        await resp.drain()
        return resp

    async def _wait_until_up(self):
        async with aiohttp.ClientSession() as session:
            for i in range(0, 30):
                if self.exitcode is not None:
                    pytest.fail('unable to start/connect to aiohttp server')
                    return

                try:
                    # we need to bypass the proxies due to monkey patches
                    await session.get(self.endpoint_url + '/ok', timeout=0.5)
                    return
                except (aiohttp.ClientConnectionError, asyncio.TimeoutError):
                    await asyncio.sleep(0.5)
                except BaseException:
                    pytest.fail('unable to start/connect to aiohttp server')
                    raise

        pytest.fail('unable to start and connect to aiohttp server')


@pytest.fixture
async def s3_server():
    async with MotoService('s3') as svc:
        yield svc.endpoint_url


@pytest.fixture
async def dynamodb2_server():
    async with MotoService('dynamodb2') as svc:
        yield svc.endpoint_url


@pytest.fixture
async def cloudformation_server():
    async with MotoService('cloudformation') as svc:
        yield svc.endpoint_url


@pytest.fixture
async def sns_server():
    async with MotoService('sns') as svc:
        yield svc.endpoint_url


@pytest.fixture
async def sqs_server():
    async with MotoService('sqs') as svc:
        yield svc.endpoint_url


@pytest.fixture
async def batch_server():
    async with MotoService('batch') as svc:
        yield svc.endpoint_url


@pytest.fixture
async def lambda_server():
    async with MotoService('lambda') as svc:
        yield svc.endpoint_url


@pytest.fixture
async def iam_server():
    async with MotoService('iam') as svc:
        yield svc.endpoint_url


@pytest.fixture
async def rds_server():
    async with MotoService('iam') as svc:
        yield svc.endpoint_url