# 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.
"""
VPS.net driver
"""

import base64

from libcloud.utils.py3 import b
from libcloud.common.base import JsonResponse, ConnectionUserAndKey
from libcloud.common.types import InvalidCredsError, MalformedResponseError
from libcloud.compute.base import Node, NodeSize, NodeImage, NodeDriver, NodeLocation
from libcloud.compute.types import NodeState
from libcloud.compute.providers import Provider

try:
    import simplejson as json
except ImportError:
    import json


API_HOST = "api.vps.net"
API_VERSION = "api10json"

RAM_PER_NODE = 256
DISK_PER_NODE = 10
BANDWIDTH_PER_NODE = 250


class VPSNetResponse(JsonResponse):
    def parse_body(self):
        try:
            return super().parse_body()
        except MalformedResponseError:
            return self.body

    def success(self):
        # vps.net wrongly uses 406 for invalid auth creds
        if self.status == 406 or self.status == 403:
            raise InvalidCredsError()
        return True

    def parse_error(self):
        try:
            errors = super().parse_body()["errors"][0]
        except MalformedResponseError:
            return self.body
        else:
            return "\n".join(errors)


class VPSNetConnection(ConnectionUserAndKey):
    """
    Connection class for the VPS.net driver
    """

    host = API_HOST
    responseCls = VPSNetResponse

    allow_insecure = False

    def add_default_headers(self, headers):
        user_b64 = base64.b64encode(b("{}:{}".format(self.user_id, self.key)))
        headers["Authorization"] = "Basic %s" % (user_b64.decode("utf-8"))
        return headers


class VPSNetNodeDriver(NodeDriver):
    """
    VPS.net node driver
    """

    type = Provider.VPSNET
    api_name = "vps_net"
    name = "vps.net"
    website = "http://vps.net/"
    connectionCls = VPSNetConnection

    def _to_node(self, vm):
        if vm["running"]:
            state = NodeState.RUNNING
        else:
            state = NodeState.PENDING

        n = Node(
            id=vm["id"],
            name=vm["label"],
            state=state,
            public_ips=[vm.get("primary_ip_address", None)],
            private_ips=[],
            extra={"slices_count": vm["slices_count"]},
            # Number of nodes consumed by VM
            driver=self.connection.driver,
        )
        return n

    def _to_image(self, image, cloud):
        image = NodeImage(
            id=image["id"],
            name="{}: {}".format(cloud, image["label"]),
            driver=self.connection.driver,
        )

        return image

    def _to_size(self, num):
        size = NodeSize(
            id=num,
            name="%d Node" % (num,),
            ram=RAM_PER_NODE * num,
            disk=DISK_PER_NODE,
            bandwidth=BANDWIDTH_PER_NODE * num,
            price=self._get_price_per_node(num) * num,
            driver=self.connection.driver,
        )
        return size

    def _get_price_per_node(self, num):
        single_node_price = self._get_size_price(size_id="1")
        return num * single_node_price

    def create_node(self, name, image, size, ex_backups_enabled=False, ex_fqdn=None):
        """Create a new VPS.net node

        @inherits: :class:`NodeDriver.create_node`

        :keyword    ex_backups_enabled: Enable automatic backups
        :type       ex_backups_enabled: ``bool``

        :keyword    ex_fqdn:   Fully Qualified domain of the node
        :type       ex_fqdn:   ``str``
        """
        ex_backups_enabled = 1 if ex_backups_enabled else 0
        headers = {"Content-Type": "application/json"}
        request = {
            "virtual_machine": {
                "label": name,
                "fqdn": ex_fqdn or "",
                "system_template_id": image.id,
                "backups_enabled": ex_backups_enabled,
                "slices_required": size.id,
            }
        }

        res = self.connection.request(
            "/virtual_machines.{}".format(API_VERSION),
            data=json.dumps(request),
            headers=headers,
            method="POST",
        )
        node = self._to_node(res.object["virtual_machine"])
        return node

    def reboot_node(self, node):
        res = self.connection.request(
            "/virtual_machines/{}/{}.{}".format(node.id, "reboot", API_VERSION),
            method="POST",
        )
        node = self._to_node(res.object["virtual_machine"])
        return True

    def list_sizes(self, location=None):
        res = self.connection.request("/nodes.{}".format(API_VERSION))
        available_nodes = len([size for size in res.object if size["slice"]["virtual_machine_id"]])
        sizes = [self._to_size(i) for i in range(1, available_nodes + 1)]
        return sizes

    def destroy_node(self, node):
        res = self.connection.request(
            "/virtual_machines/{}.{}".format(node.id, API_VERSION), method="DELETE"
        )
        return res.status == 200

    def list_nodes(self):
        res = self.connection.request("/virtual_machines.{}".format(API_VERSION))
        return [self._to_node(i["virtual_machine"]) for i in res.object]

    def list_images(self, location=None):
        res = self.connection.request("/available_clouds.{}".format(API_VERSION))

        images = []
        for cloud in res.object:
            label = cloud["cloud"]["label"]
            templates = cloud["cloud"]["system_templates"]
            images.extend([self._to_image(image, label) for image in templates])

        return images

    def list_locations(self):
        return [NodeLocation(0, "VPS.net Western US", "US", self)]
