import asyncio
import ssl

import aiohttp

from . import CloudStack, CloudStackApiException, CloudStackException
from .client import PENDING, SUCCESS, transform


class AIOCloudStack(CloudStack):
    def __getattr__(self, command):
        def handler(**kwargs):
            return self._request(command, **kwargs)
        return handler

    async def _request(self, command, json=True, opcode_name='command',
                       fetch_list=False, headers=None, **params):
        fetch_result = params.pop("fetch_result", self.fetch_result)
        kwarg, kwargs = self._prepare_request(command, json, opcode_name,
                                              fetch_list, **params)

        ssl_context = None
        if self.cert:
            ssl_context = ssl.create_default_context(cafile=self.cert)
        connector = aiohttp.TCPConnector(verify_ssl=self.verify,
                                         ssl_context=ssl_context)

        async with aiohttp.ClientSession(read_timeout=self.timeout,
                                         conn_timeout=self.timeout,
                                         connector=connector) as session:
            handler = getattr(session, self.method)

            done = False
            final_data = []
            page = 1
            while not done:
                if fetch_list:
                    kwargs['page'] = page

                transform(kwargs)
                kwargs.pop('signature', None)
                self._sign(kwargs)
                response = await handler(self.endpoint,
                                         headers=headers,
                                         **{kwarg: kwargs})

                ctype = response.headers['content-type'].split(';')[0]
                try:
                    data = await response.json(content_type=ctype)
                except ValueError as e:
                    msg = "Make sure endpoint URL {!r} is correct.".format(
                        self.endpoint)
                    raise CloudStackException(
                        "HTTP {0} response from CloudStack".format(
                            response.status),
                        "{}. {}".format(e, msg),
                        response=response,
                    )

                [key] = data.keys()
                data = data[key]
                if response.status != 200:
                    raise CloudStackApiException(
                        "HTTP {0} response from CloudStack".format(
                            response.status), error=data, response=response)
                if fetch_list:
                    try:
                        [key] = [k for k in data.keys() if k != 'count']
                    except ValueError:
                        done = True
                    else:
                        final_data.extend(data[key])
                        page += 1
                elif fetch_result and 'jobid' in data:
                    try:
                        final_data = await asyncio.wait_for(
                            self._jobresult(data['jobid'], response),
                            self.job_timeout)
                    except asyncio.TimeoutError:
                        raise CloudStackException(
                            "Timeout waiting for async job result",
                            data['jobid'],
                            response=response)
                    done = True
                else:
                    final_data = data
                    done = True
            return final_data

    async def _jobresult(self, jobid, response):
        failures = 0
        while True:
            try:
                j = await self.queryAsyncJobResult(jobid=jobid,
                                                   fetch_result=False)
                failures = 0
                if j['jobstatus'] != PENDING:
                    if j['jobresultcode'] != 0 or j['jobstatus'] != SUCCESS:
                        raise CloudStackApiException("Job failure", j,
                                                     error=j['jobresult'],
                                                     response=response)
                    if 'jobresult' not in j:
                        raise CloudStackException("Unknown job result", j,
                                                  response=response)
                    return j['jobresult']

            except CloudStackException:
                raise

            except Exception:
                failures += 1
                if failures > 10:
                    raise

            await asyncio.sleep(self.poll_interval)
