# 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 time
import hashlib

from libcloud.utils.py3 import b
from libcloud.common.base import JsonResponse, ConnectionUserAndKey
from libcloud.common.types import LibcloudError, InvalidCredsError, MalformedResponseError
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().__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().request(action, params, data, headers, method, raw)


class GoGridIpAddress:
    """
    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:
    """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)
