# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License.  You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import hashlib
import time

from libcloud.utils.py3 import b

from libcloud.common.types import InvalidCredsError, LibcloudError
from libcloud.common.types import MalformedResponseError
from libcloud.common.base import ConnectionUserAndKey, JsonResponse
from libcloud.compute.base import NodeLocation

HOST = 'api.gogrid.com'
PORTS_BY_SECURITY = {True: 443, False: 80}
API_VERSION = '1.8'

__all__ = [
    "GoGridResponse",
    "GoGridConnection",
    "GoGridIpAddress",
    "BaseGoGridDriver",
]


class GoGridResponse(JsonResponse):

    def __init__(self, *args, **kwargs):
        self.driver = BaseGoGridDriver
        super(GoGridResponse, self).__init__(*args, **kwargs)

    def success(self):
        if self.status == 403:
            raise InvalidCredsError('Invalid credentials', self.driver)
        if self.status == 401:
            raise InvalidCredsError('API Key has insufficient rights',
                                    self.driver)
        if not self.body:
            return None
        try:
            return self.parse_body()['status'] == 'success'
        except ValueError:
            raise MalformedResponseError('Malformed reply',
                                         body=self.body,
                                         driver=self.driver)

    def parse_error(self):
        try:
            return self.parse_body()["list"][0]["message"]
        except (ValueError, KeyError):
            return None


class GoGridConnection(ConnectionUserAndKey):
    """
    Connection class for the GoGrid driver
    """

    host = HOST
    responseCls = GoGridResponse

    def add_default_params(self, params):
        params["api_key"] = self.user_id
        params["v"] = API_VERSION
        params["format"] = 'json'
        params["sig"] = self.get_signature(self.user_id, self.key)

        return params

    def get_signature(self, key, secret):
        """ create sig from md5 of key + secret + time """
        m = hashlib.md5(b(key + secret + str(int(time.time()))))
        return m.hexdigest()

    def request(self, action, params=None, data='', headers=None, method='GET',
                raw=False):
        return super(GoGridConnection, self).request(action, params, data,
                                                     headers, method, raw)


class GoGridIpAddress(object):
    """
    IP Address
    """

    def __init__(self, id, ip, public, state, subnet):
        self.id = id
        self.ip = ip
        self.public = public
        self.state = state
        self.subnet = subnet


class BaseGoGridDriver(object):
    """GoGrid has common object model for services they
    provide, like locations and IP, so keep handling of
    these things in a single place."""

    name = "GoGrid"

    def _get_ip(self, element):
        return element.get('ip').get('ip')

    def _to_ip(self, element):
        ip = GoGridIpAddress(id=element['id'],
                             ip=element['ip'],
                             public=element['public'],
                             subnet=element['subnet'],
                             state=element["state"]["name"])
        ip.location = self._to_location(element['datacenter'])
        return ip

    def _to_ips(self, object):
        return [self._to_ip(el)
                for el in object['list']]

    def _to_location(self, element):
        # pylint: disable=no-member
        location = NodeLocation(id=element['id'],
                                name=element['name'],
                                country="US",
                                driver=self.connection.driver)
        return location

    def _to_locations(self, object):
        return [self._to_location(el)
                for el in object['list']]

    def ex_list_ips(self, **kwargs):
        """Return list of IP addresses assigned to
        the account.

        :keyword    public: set to True to list only
                    public IPs or False to list only
                    private IPs. Set to None or not specify
                    at all not to filter by type
        :type       public: ``bool``

        :keyword    assigned: set to True to list only addresses
                    assigned to servers, False to list unassigned
                    addresses and set to None or don't set at all
                    not no filter by state
        :type       assigned: ``bool``

        :keyword    location: filter IP addresses by location
        :type       location: :class:`NodeLocation`

        :rtype: ``list`` of :class:`GoGridIpAddress`
        """

        params = {}

        if "public" in kwargs and kwargs["public"] is not None:
            params["ip.type"] = {True: "Public",
                                 False: "Private"}[kwargs["public"]]
        if "assigned" in kwargs and kwargs["assigned"] is not None:
            params["ip.state"] = {True: "Assigned",
                                  False: "Unassigned"}[kwargs["assigned"]]
        if "location" in kwargs and kwargs['location'] is not None:
            params['datacenter'] = kwargs['location'].id

        # pylint: disable=no-member
        response = self.connection.request('/api/grid/ip/list', params=params)
        ips = self._to_ips(response.object)
        return ips

    def _get_first_ip(self, location=None):
        ips = self.ex_list_ips(public=True, assigned=False, location=location)
        try:
            return ips[0].ip
        except IndexError:
            # pylint: disable=no-member
            raise LibcloudError('No public unassigned IPs left',
                                self.driver)
