# Copyright 2014
# The Cloudscaling Group, Inc.
#
# Licensed 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 string

from oslo_log import log as logging

from gceapi.api import base_api
from gceapi.api import clients
from gceapi.api import disk_api
from gceapi.api import operation_api
from gceapi.api import operation_util
from gceapi.api import utils
from gceapi import exception
from gceapi.i18n import _

LOG = logging.getLogger(__name__)

GB = 1024 ** 3


class API(base_api.API):
    """GCE Attached disk API."""

    KIND = "attached_disk"
    PERSISTENT_ATTRIBUTES = ["id", "instance_name", "volume_id", "name",
                             "auto_delete"]

    def __init__(self, *args, **kwargs):
        super(API, self).__init__(*args, **kwargs)
        operation_api.API().register_get_progress_method(
                "attached_disk-delete",
                self._get_delete_item_progress)

    def _get_type(self):
        return self.KIND

    def _get_persistent_attributes(self):
        return self.PERSISTENT_ATTRIBUTES

    def get_item(self, context, instance_name, name):
        items = self._get_db_items(context)
        items = [i for i in items
                if i["instance_name"] == instance_name and i["name"] == name]
        if len(items) != 1:
            raise exception.NotFound
        return items[0]

    def get_items(self, context, instance_name):
        items = self._get_db_items(context)
        for item in items:
            item.setdefault("auto_delete", False)
        return [i for i in items if i["instance_name"] == instance_name]

    def add_item(self, context, instance_name, params, source, name,
                 auto_delete, scope):
        # NOTE(apavlov): name is a 'device_name' here
        if not name:
            msg = _("There is no name to assign.")
            raise exception.InvalidRequest(msg)

        nova_client = clients.nova(context)
        instances = nova_client.servers.list(
            search_opts={"name": instance_name})
        if not instances or len(instances) != 1:
            raise exception.NotFound
        instance = instances[0]

        devices = list()
        volumes_client = nova_client.volumes
        for server_volume in volumes_client.get_server_volumes(instance.id):
            devices.append(server_volume.device)
        device_name = None
        for letter in string.ascii_lowercase[1:]:
            device_name = "vd" + letter
            for device in devices:
                if device_name in device:
                    break
            else:
                break
        else:
            raise exception.OverQuota
        context.operation_data["device_name"] = device_name

        if source:
            volume_name = utils._extract_name_from_url(source)
            if not volume_name:
                msg = _("There is no volume to assign.")
                raise exception.NotFound(msg)
            volume = disk_api.API().get_item(context, volume_name, scope)
            context.operation_data["volume_id"] = volume["id"]
        elif params:
            params.setdefault("diskName", instance_name)
            context.operation_data["params"] = params
            context.operation_data["scope"] = scope
        else:
            msg = _('Disk config must contain either "source" or '
                    '"initializeParams".')
            raise exception.InvalidRequest(msg)

        context.operation_data["instance_id"] = instance.id
        context.operation_data["register_args"] = [instance_name, name,
                                                   auto_delete]
        operation_util.start_operation(
            context, base_api.API._get_complex_operation_progress)
        operation_util.continue_operation(
            context, lambda: self._attach_volume(context), timeout=0)

    def _attach_volume(self, context):
        params = context.operation_data.get("params")
        if params:
            scope = context.operation_data["scope"]
            context.operation_data.pop("params")
            body = {"sizeGb": params.get("diskSizeGb"),
                    "sourceImage": params["sourceImage"]}
            volume = disk_api.API().add_item(context, params.get("diskName"),
                                             body, scope=scope)
            context.operation_data["disk"] = volume
            return None

        disk = context.operation_data.get("disk")
        if disk:
            volume_id = disk["id"]
            item_progress = disk_api.API()._get_add_item_progress(context,
                                                                  volume_id)
            if not operation_util.is_final_progress(item_progress):
                return None
            context.operation_data.pop("disk")
            context.operation_data["volume_id"] = volume_id

        instance_id = context.operation_data["instance_id"]
        device_name = context.operation_data["device_name"]
        volume_id = context.operation_data["volume_id"]
        volumes_client = clients.nova(context).volumes
        volumes_client.create_server_volume(
                instance_id, volume_id, "/dev/" + device_name)

        args = context.operation_data["register_args"]
        self.register_item(context, args[0], volume_id, args[1], args[2])

        return operation_util.get_final_progress()

    def register_item(self, context, instance_name, volume_id, name,
                      auto_delete):
        if not name:
            msg = _("There is no name to assign.")
            raise exception.InvalidRequest(msg)
        if not volume_id:
            msg = _("There is no volume_id to assign.")
            raise exception.InvalidRequest(msg)

        new_item = {
            "id": instance_name + "-" + volume_id,
            "instance_name": instance_name,
            "volume_id": volume_id,
            "name": name,
            "auto_delete": auto_delete
        }
        new_item = self._add_db_item(context, new_item)
        return new_item

    def delete_item(self, context, instance_name, name):
        item = self.get_item(context, instance_name, name)
        volume_id = item["volume_id"]

        nova_client = clients.nova(context)
        instances = nova_client.servers.list(
            search_opts={"name": instance_name})
        if not instances or len(instances) != 1:
            raise exception.NotFound
        instance = instances[0]

        operation_util.start_operation(context,
                                       self._get_delete_item_progress,
                                       item["id"])
        nova_client.volumes.delete_server_volume(instance.id, volume_id)

        self._delete_db_item(context, item)

    def set_disk_auto_delete(self, context, instance_name, name, auto_delete):
        item = self.get_item(context, instance_name, name)
        item["auto_delete"] = auto_delete
        self._update_db_item(context, item)

    def unregister_item(self, context, instance_name, name):
        item = self.get_item(context, instance_name, name)
        self._delete_db_item(context, item)

    def _get_add_item_progress(self, context, dummy_id):
        return operation_util.get_final_progress()

    def _get_delete_item_progress(self, context, dummy_id):
        return operation_util.get_final_progress()