# 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.
from functools import partial

from karbor.common import constants
from karbor.tests.fullstack import karbor_base as base
from karbor.tests.fullstack import utils

SHORT_TIMEOUT = 150
MEDIUM_TIMEOUT = 450
LONG_TIMEOUT = 900

SHORT_SLEEP = 3
MEDIUM_SLEEP = 15
LONG_SLEEP = 30
HUGE_SLEEP = 100

DEFAULT_FLAVOR = "cirros256"
DEFAULT_NETWORK = "private"


class Checkpoint(object):
    def __init__(self):
        super(Checkpoint, self).__init__()
        self.id = None
        self._provider_id = None
        self.karbor_client = base._get_karbor_client()

    def _checkpoint_status(self, status=None):
        try:
            cp = self.karbor_client.checkpoints.get(self._provider_id, self.id)
        except Exception:
            return False

        if status is None or cp.status == status:
            return True
        else:
            return False

    def create(self, provider_id, plan_id, timeout=LONG_TIMEOUT):
        self._provider_id = provider_id
        checkpoint = self.karbor_client.checkpoints.create(provider_id,
                                                           plan_id)
        self.id = checkpoint.id
        utils.wait_until_true(partial(self._checkpoint_status,
                                      constants.CHECKPOINT_STATUS_AVAILABLE),
                              timeout=timeout, sleep=HUGE_SLEEP)
        return self.id

    def close(self, timeout=MEDIUM_TIMEOUT):
        try:
            self.karbor_client.checkpoints.delete(self._provider_id, self.id)
        except Exception:
            return
        utils.wait_until_true(partial(self._checkpoint_status,
                                      constants.CHECKPOINT_STATUS_DELETED),
                              timeout=timeout, sleep=LONG_SLEEP)


class Plan(object):
    _name_id = 0

    def __init__(self):
        super(Plan, self).__init__()
        self.id = None
        self.karbor_client = base._get_karbor_client()

    def create(self, provider_id, resources,
               parameters={}, name=None):
        def _transform_resource(resource):
            if isinstance(resource, dict):
                return resource
            if hasattr(resource, 'to_dict') and callable(resource.to_dict):
                return resource.to_dict()

        if name is None:
            name = "KarborFullstack-Plan-{id}".format(
                id=self.__class__._name_id
            )
            self.__class__._name_id += 1

        resources = map(_transform_resource, resources)
        plan = self.karbor_client.plans.create(name, provider_id, resources,
                                               parameters)
        self.id = plan.id
        return self.id

    def update(self, data):
        return self.karbor_client.plans.update(self.id, data)

    def close(self):
        try:
            self.karbor_client.plans.delete(self.id)
        except Exception:
            return


class Restore(object):
    def __init__(self):
        super(Restore, self).__init__()
        self.id = None
        self.karbor_client = base._get_karbor_client()

    def _restore_status(self, status=None):
        try:
            restore = self.karbor_client.restores.get(self.id)
        except Exception:
            return False

        if status is None or restore.status == status:
            return True
        else:
            return False

    def create(self, provider_id, checkpoint_id, target, parameters,
               restore_auth, timeout=LONG_TIMEOUT):
        restore = self.karbor_client.restores.create(provider_id,
                                                     checkpoint_id,
                                                     target,
                                                     parameters,
                                                     restore_auth)
        self.id = restore.id
        utils.wait_until_true(partial(self._restore_status, 'success'),
                              timeout=timeout, sleep=HUGE_SLEEP)
        return self.id

    def close(self):
        pass


class Trigger(object):
    _name_id = 0

    def __init__(self):
        super(Trigger, self).__init__()
        self.id = None
        self.karbor_client = base._get_karbor_client()

    def create(self, type, properties, name=None):
        if name is None:
            name = "KarborFullstack-Trigger-{id}".format(
                id=self.__class__._name_id
            )
            self.__class__._name_id += 1

        trigger = self.karbor_client.triggers.create(name, type, properties)
        self.id = trigger.id
        return self.id

    def close(self):
        try:
            self.karbor_client.triggers.delete(self.id)
        except Exception:
            return


class ScheduledOperation(object):
    _name_id = 0

    def __init__(self):
        super(ScheduledOperation, self).__init__()
        self.id = None
        self.karbor_client = base._get_karbor_client()

    def create(self, operation_type, trigger_id,
               operation_definition, name=None):
        if name is None:
            name = "KarborFullstack-Scheduled-Operation-{id}".format(
                id=self.__class__._name_id
            )
            self.__class__._name_id += 1

        scheduled_operation = self.karbor_client.scheduled_operations.create(
            name,
            operation_type,
            trigger_id,
            operation_definition
        )
        self.id = scheduled_operation.id
        return self.id

    def close(self):
        try:
            self.karbor_client.scheduled_operations.delete(self.id)
        except Exception:
            return


class Server(object):
    _name_id = 0

    def __init__(self):
        super(Server, self).__init__()
        self.id = None
        self._name = None
        self.nova_client = base._get_nova_client()
        self.neutron_client = base._get_neutron_client()
        self.cinder_client = base._get_cinder_client()
        self.glance_client = base._get_glance_client()

    def _server_status(self, status=None):
        try:
            server = self.nova_client.servers.get(self.id)
        except Exception:
            return False

        if status is None or status == server.status:
            return True
        else:
            return False

    def to_dict(self):
        return {
            "id": self.id,
            "type": constants.SERVER_RESOURCE_TYPE,
            "name": self._name,
        }

    def create(self, name=None, image=None, volume=None, flavor=DEFAULT_FLAVOR,
               network=DEFAULT_NETWORK, timeout=LONG_TIMEOUT):
        block_device_mapping_v2 = None
        if volume:
            block_device_mapping_v2 = [{
                'uuid': volume,
                'source_type': 'volume',
                'destination_type': 'volume',
                'boot_index': 0,
                'delete_on_termination': False}]
        else:
            if not image:
                images = self.glance_client.images.list()
                for image_iter in images:
                    if image_iter['disk_format'] not in ('aki', 'ari') and (
                            image_iter['name'].startswith('cirros')):
                        image = image_iter['id']
                        break
            assert image
        flavor = self.nova_client.flavors.find(name=flavor)
        if name is None:
            name = "KarborFullstack-Server-{id}".format(
                id=self.__class__._name_id
            )
            self.__class__._name_id += 1
            self._name = name

        networks = self.neutron_client.list_networks(name=network)
        assert len(networks['networks']) > 0
        network_id = networks['networks'][0]['id']

        server = self.nova_client.servers.create(
            name=name,
            image=image,
            block_device_mapping_v2=block_device_mapping_v2,
            flavor=flavor,
            nics=[{"net-id": network_id}],
        )
        self.id = server.id

        utils.wait_until_true(partial(self._server_status, 'ACTIVE'),
                              timeout=timeout, sleep=MEDIUM_SLEEP)
        return self.id

    def _volume_attached(self, volume_id):
        volume_item = self.cinder_client.volumes.get(volume_id)
        server_attachments = filter(lambda x: x['server_id'] == self.id,
                                    volume_item.attachments)
        if len(server_attachments) > 0:
            return True
        else:
            return False

    def attach_volume(self, volume_id, timeout=MEDIUM_TIMEOUT):
        self.nova_client.volumes.create_server_volume(self.id, volume_id)
        utils.wait_until_true(partial(self._volume_attached, volume_id),
                              timeout=timeout, sleep=SHORT_SLEEP)

    def _volume_detached(self, volume_id):
        volume_item = self.cinder_client.volumes.get(volume_id)
        server_attachments = filter(lambda x: x['server_id'] == self.id,
                                    volume_item.attachments)
        if len(server_attachments) > 0:
            return False
        else:
            return True

    def detach_volume(self, volume_id, timeout=MEDIUM_TIMEOUT):
        self.nova_client.volumes.delete_server_volume(self.id, volume_id)
        utils.wait_until_true(partial(self._volume_detached, volume_id),
                              timeout=timeout, sleep=SHORT_SLEEP)

    def close(self, timeout=MEDIUM_TIMEOUT):
        try:
            self.nova_client.servers.delete(self.id)
        except Exception:
            return
        utils.wait_until_none(self._server_status, timeout=timeout,
                              sleep=MEDIUM_SLEEP)


class Volume(object):
    _name_id = 0

    def __init__(self):
        super(Volume, self).__init__()
        self.id = None
        self._name = None
        self.cinder_client = base._get_cinder_client()
        self.glance_client = base._get_glance_client()

    def _volume_status(self, status=None):
        try:
            volume = self.cinder_client.volumes.get(self.id)
        except Exception:
            return False

        if status is None or status == volume.status:
            return True
        else:
            return False

    def to_dict(self):
        return {
            "id": self.id,
            "type": constants.VOLUME_RESOURCE_TYPE,
            "name": self._name,
            "extra_info": {'availability_zone': 'az1'},
        }

    def create(self, size, name=None, create_from_image=False,
               timeout=LONG_TIMEOUT):
        if name is None:
            name = "KarborFullstack-Volume-{id}".format(
                id=self.__class__._name_id
            )
            self.__class__._name_id += 1

        self._name = name
        image = None
        if create_from_image:
            images = self.glance_client.images.list()
            for image_iter in images:
                if image_iter['disk_format'] not in ('aki', 'ari') and (
                        image_iter['name'].startswith('cirros')):
                    image = image_iter['id']
                    break
            assert image
        volume = self.cinder_client.volumes.create(size, name=name,
                                                   imageRef=image)
        self.id = volume.id
        utils.wait_until_true(partial(self._volume_status, 'available'),
                              timeout=timeout, sleep=MEDIUM_SLEEP)
        return self.id

    def close(self, timeout=LONG_TIMEOUT):
        try:
            self.cinder_client.volumes.delete(self.id)
        except Exception:
            return
        utils.wait_until_none(self._volume_status, timeout=timeout,
                              sleep=MEDIUM_SLEEP)


class Share(object):
    _name_id = 0

    def __init__(self):
        super(Share, self).__init__()
        self.id = None
        self._name = None
        self.manila_client = base._get_manila_client()
        self.neutron_client = base._get_neutron_client()

    def _share_status(self, status=None):
        try:
            share = self.manila_client.shares.get(self.id)
        except Exception:
            return False

        if status is None or status == share.status:
            return True
        else:
            return False

    def to_dict(self):
        return {
            "id": self.id,
            "type": constants.SHARE_RESOURCE_TYPE,
            "name": self._name,
        }

    def create(self, share_proto, size, name=None, timeout=LONG_TIMEOUT):
        if name is None:
            name = "KarborFullstack-Share-{id}".format(id=self._name_id)
            self._name_id += 1

        self._name = name
        share = self.manila_client.shares.create(share_proto, size, name=name)
        self.id = share.id
        utils.wait_until_true(partial(self._share_status, 'available'),
                              timeout=timeout, sleep=MEDIUM_SLEEP)
        return self.id

    def close(self, timeout=MEDIUM_TIMEOUT):
        try:
            self.manila_client.shares.delete(self.id)
        except Exception:
            return
        utils.wait_until_none(self._share_status, timeout=timeout,
                              sleep=MEDIUM_SLEEP)


class Network(object):
    def __init__(self):
        super(Network, self).__init__()
        self.id = None
        self.project_id = None
        self._name = "private-net"
        self.neutron_client = base._get_neutron_client()

    def _network_status(self, status=None):
        try:
            networks = self.neutron_client.list_networks(name=self._name)
            assert len(networks['networks']) > 0
            network = networks['networks'][0]
        except Exception:
            return False

        if status is None or status == network['status']:
            return True
        else:
            return False

    def to_dict(self):
        return {
            "id": self.id,
            "type": constants.NETWORK_RESOURCE_TYPE,
            "name": self._name,
        }

    def create(self, timeout=MEDIUM_TIMEOUT):
        network = {'name': self._name, 'admin_state_up': True}
        self.neutron_client.create_network({'network': network})

        networks = self.neutron_client.list_networks(name=self._name)
        assert len(networks['networks']) > 0
        network_id = networks['networks'][0]['id']
        self.id = network_id
        self.project_id = networks['networks'][0]['tenant_id']

        utils.wait_until_true(partial(self._network_status, 'ACTIVE'),
                              timeout=timeout, sleep=MEDIUM_SLEEP)

        return self.id

    def close(self, timeout=LONG_TIMEOUT):
        try:
            self.neutron_client.delete_network(self.id)
        except Exception:
            return
        utils.wait_until_none(self._network_status, timeout=timeout,
                              sleep=MEDIUM_SLEEP)
