#!/usr/bin/env python
# -*- coding: utf-8 -*-


from math import ceil

from flask import Blueprint, request, url_for
import json
import requests
from uuid import uuid4
import jimit as ji

from jimvc.models import Guest, DiskState, Host
from jimvc.models.initialize import dev_table
from jimvc.models import Config
from jimvc.models import Disk
from jimvc.models import Rules
from jimvc.models import Utils
from jimvc.models import OSTemplateImage
from jimvc.models import StorageMode

from base import Base


__author__ = 'James Iter'
__date__ = '2017/4/24'
__contact__ = 'james.iter.cn@gmail.com'
__copyright__ = '(c) 2017 by James Iter.'


blueprint = Blueprint(
    'api_disk',
    __name__,
    url_prefix='/api/disk'
)

blueprints = Blueprint(
    'api_disks',
    __name__,
    url_prefix='/api/disks'
)


disk_base = Base(the_class=Disk, the_blueprint=blueprint, the_blueprints=blueprints)


@Utils.dumps2response
def r_create():

    args_rules = [
        Rules.DISK_SIZE.value,
        Rules.REMARK.value,
        Rules.QUANTITY.value
    ]

    config = Config()
    config.id = 1
    config.get()

    # 非共享模式，必须指定 node_id
    if config.storage_mode not in [StorageMode.shared_mount.value, StorageMode.ceph.value,
                                   StorageMode.glusterfs.value]:
        args_rules.append(
            Rules.NODE_ID.value
        )

    try:
        ji.Check.previewing(args_rules, request.json)

        size = request.json['size']
        quantity = request.json['quantity']

        ret = dict()
        ret['state'] = ji.Common.exchange_state(20000)

        # 如果是共享模式，则让负载最轻的计算节点去创建磁盘
        if config.storage_mode in [StorageMode.shared_mount.value, StorageMode.ceph.value,
                                   StorageMode.glusterfs.value]:
            available_hosts = Host.get_available_hosts()

            if available_hosts.__len__() == 0:
                ret['state'] = ji.Common.exchange_state(50351)
                return ret

            # 在可用计算节点中平均分配任务
            chosen_host = available_hosts[quantity % available_hosts.__len__()]
            request.json['node_id'] = chosen_host['node_id']

        node_id = request.json['node_id']

        if size < 1:
            ret['state'] = ji.Common.exchange_state(41255)
            return ret

        while quantity:
            quantity -= 1
            disk = Disk()
            disk.guest_uuid = ''
            disk.size = size
            disk.uuid = uuid4().__str__()
            disk.remark = request.json.get('remark', '')
            disk.node_id = int(node_id)
            disk.sequence = -1
            disk.format = 'qcow2'
            disk.path = config.storage_path + '/' + disk.uuid + '.' + disk.format
            disk.quota(config=config)

            message = {
                '_object': 'disk',
                'action': 'create',
                'uuid': disk.uuid,
                'storage_mode': config.storage_mode,
                'dfs_volume': config.dfs_volume,
                'node_id': disk.node_id,
                'image_path': disk.path,
                'size': disk.size
            }

            Utils.emit_instruction(message=json.dumps(message, ensure_ascii=False))

            disk.create()

        return ret

    except ji.PreviewingError, e:
        return json.loads(e.message)


@Utils.dumps2response
def r_resize(uuid, size):

    args_rules = [
        Rules.UUID.value,
        Rules.DISK_SIZE_STR.value
    ]

    try:
        ji.Check.previewing(args_rules, {'uuid': uuid, 'size': size})

        disk = Disk()
        disk.uuid = uuid
        disk.get_by('uuid')

        ret = dict()
        ret['state'] = ji.Common.exchange_state(20000)

        if disk.size >= int(size):
            ret['state'] = ji.Common.exchange_state(41257)
            return ret

        config = Config()
        config.id = 1
        config.get()

        disk.size = int(size)
        disk.quota(config=config)
        # 将在事件返回层(models/event_processor.py:224 附近)，更新数据库中 disk 对象

        message = {
            '_object': 'disk',
            'action': 'resize',
            'uuid': disk.uuid,
            'guest_uuid': disk.guest_uuid,
            'storage_mode': config.storage_mode,
            'size': disk.size,
            'dfs_volume': config.dfs_volume,
            'node_id': disk.node_id,
            'image_path': disk.path,
            'disks': [disk.__dict__],
            'passback_parameters': {'size': disk.size}
        }

        if config.storage_mode in [StorageMode.shared_mount.value, StorageMode.ceph.value,
                                   StorageMode.glusterfs.value]:
            message['node_id'] = Host.get_lightest_host()['node_id']

        if disk.guest_uuid.__len__() == 36:
            message['device_node'] = dev_table[disk.sequence]

        Utils.emit_instruction(message=json.dumps(message, ensure_ascii=False))

        return ret

    except ji.PreviewingError, e:
        return json.loads(e.message)


@Utils.dumps2response
def r_delete(uuids):

    args_rules = [
        Rules.UUIDS.value
    ]

    try:
        ji.Check.previewing(args_rules, {'uuids': uuids})

        ret = dict()
        ret['state'] = ji.Common.exchange_state(20000)

        disk = Disk()

        # 检测所指定的 UUDIs 磁盘都存在
        for uuid in uuids.split(','):
            disk.uuid = uuid
            disk.get_by('uuid')

            # 判断磁盘是否与虚拟机处于离状态
            if disk.state not in [DiskState.idle.value, DiskState.dirty.value]:
                ret['state'] = ji.Common.exchange_state(41256)
                return ret

        config = Config()
        config.id = 1
        config.get()

        # 执行删除操作
        for uuid in uuids.split(','):
            disk.uuid = uuid
            disk.get_by('uuid')

            message = {
                '_object': 'disk',
                'action': 'delete',
                'uuid': disk.uuid,
                'storage_mode': config.storage_mode,
                'dfs_volume': config.dfs_volume,
                'node_id': disk.node_id,
                'image_path': disk.path
            }

            if config.storage_mode in [StorageMode.shared_mount.value, StorageMode.ceph.value,
                                       StorageMode.glusterfs.value]:
                message['node_id'] = Host.get_lightest_host()['node_id']

            Utils.emit_instruction(message=json.dumps(message, ensure_ascii=False))

        return ret

    except ji.PreviewingError, e:
        return json.loads(e.message)


def add_device(func):
    from functools import wraps

    @wraps(func)
    def _add_device(*args, **kwargs):
        ret = func(*args, **kwargs)
        if ret['data'].__len__() > 0:
            if isinstance(ret['data'], list):
                for i, item in enumerate(ret['data']):
                    ret['data'][i][u'device'] = u'/dev/' + dev_table[item['sequence']]

                    if item['sequence'] < 0:
                        ret['data'][i][u'device'] = None

            elif isinstance(ret['data'], dict):
                ret['data'][u'device'] = u'/dev/' + dev_table[ret['data']['sequence']]

                if ret['data']['sequence'] < 0:
                    ret['data'][u'device'] = None

            else:
                raise json.dumps(ret)

        return ret

    return _add_device


@Utils.dumps2response
@add_device
def r_get(uuids):
    return disk_base.get(ids=uuids, ids_rule=Rules.UUIDS.value, by_field='uuid')


@Utils.dumps2response
@add_device
def r_get_by_filter():
    return disk_base.get_by_filter()


@Utils.dumps2response
@add_device
def r_content_search():
    return disk_base.content_search()


@Utils.dumps2response
def r_update(uuids):

    ret = dict()
    ret['state'] = ji.Common.exchange_state(20000)
    ret['data'] = list()

    args_rules = [
        Rules.UUIDS.value
    ]

    if 'remark' in request.json:
        args_rules.append(
            Rules.REMARK.value
        )

    if 'iops' in request.json:
        args_rules.append(
            Rules.IOPS.value
        )

    if 'iops_rd' in request.json:
        args_rules.append(
            Rules.IOPS_RD.value
        )

    if 'iops_wr' in request.json:
        args_rules.append(
            Rules.IOPS_WR.value
        )

    if 'iops_max' in request.json:
        args_rules.append(
            Rules.IOPS_MAX.value
        )

    if 'iops_max_length' in request.json:
        args_rules.append(
            Rules.IOPS_MAX_LENGTH.value
        )

    if 'bps' in request.json:
        args_rules.append(
            Rules.BPS.value
        )

    if 'bps_rd' in request.json:
        args_rules.append(
            Rules.BPS_RD.value
        )

    if 'bps_wr' in request.json:
        args_rules.append(
            Rules.BPS_WR.value
        )

    if 'bps_max' in request.json:
        args_rules.append(
            Rules.BPS_MAX.value
        )

    if 'bps_max_length' in request.json:
        args_rules.append(
            Rules.BPS_MAX_LENGTH.value
        )

    if args_rules.__len__() < 2:
        return ret

    request.json['uuids'] = uuids

    need_update_quota = False
    need_update_quota_parameters = ['iops', 'iops_rd', 'iops_wr', 'iops_max', 'iops_max_length',
                                    'bps', 'bps_rd', 'bps_wr', 'bps_max', 'bps_max_length']

    if filter(lambda p: p in request.json, need_update_quota_parameters).__len__() > 0:
        need_update_quota = True

    try:
        ji.Check.previewing(args_rules, request.json)

        disk = Disk()

        # 检测所指定的 UUDIs 磁盘都存在
        for uuid in uuids.split(','):
            disk.uuid = uuid
            disk.get_by('uuid')

        for uuid in uuids.split(','):
            disk.uuid = uuid
            disk.get_by('uuid')
            disk.remark = request.json.get('remark', disk.remark)
            disk.iops = request.json.get('iops', disk.iops)
            disk.iops_rd = request.json.get('iops_rd', disk.iops_rd)
            disk.iops_wr = request.json.get('iops_wr', disk.iops_wr)
            disk.iops_max = request.json.get('iops_max', disk.iops_max)
            disk.iops_max_length = request.json.get('iops_max_length', disk.iops_max_length)
            disk.bps = request.json.get('bps', disk.bps)
            disk.bps_rd = request.json.get('bps_rd', disk.bps_rd)
            disk.bps_wr = request.json.get('bps_wr', disk.bps_wr)
            disk.bps_max = request.json.get('bps_max', disk.bps_max)
            disk.bps_max_length = request.json.get('bps_max_length', disk.bps_max_length)
            disk.update()
            disk.get()

            if disk.sequence >= 0 and need_update_quota:
                message = {
                    '_object': 'disk',
                    'action': 'quota',
                    'uuid': disk.uuid,
                    'guest_uuid': disk.guest_uuid,
                    'node_id': disk.node_id,
                    'disks': [disk.__dict__]
                }

                Utils.emit_instruction(message=json.dumps(message))

            ret['data'].append(disk.__dict__)

        return ret

    except ji.PreviewingError, e:
        return json.loads(e.message)


@Utils.dumps2response
def r_distribute_count():
    from jimvc.models import Disk
    rows, count = Disk.get_all()

    ret = dict()
    ret['state'] = ji.Common.exchange_state(20000)

    ret['data'] = {
        'kind': {'system': 0, 'data_mounted': 0, 'data_idle': 0},
        'total_size': 0,
        'disks': rows.__len__()
    }

    for disk in rows:
        if disk['sequence'] == 0:
            ret['data']['kind']['system'] += 1

        elif disk['sequence'] < 0:
            ret['data']['kind']['data_idle'] += 1

        else:
            ret['data']['kind']['data_mounted'] += 1

        ret['data']['total_size'] += disk['size']

    return ret


@Utils.dumps2response
def r_show():
    args = list()

    page = request.args.get('page', 1)
    if page == '':
        page = 1
    page = int(page)

    page_size = int(request.args.get('page_size', 10))
    keyword = request.args.get('keyword', None)
    show_area = request.args.get('show_area', 'unmount')
    guest_uuid = request.args.get('guest_uuid', None)
    sequence = request.args.get('sequence', None)
    order_by = request.args.get('order_by', None)
    order = request.args.get('order', None)
    filters = list()

    if page is not None:
        args.append('page=' + page.__str__())

    if page_size is not None:
        args.append('page_size=' + page_size.__str__())

    if keyword is not None:
        args.append('keyword=' + keyword.__str__())

    if guest_uuid is not None:
        filters.append('guest_uuid:in:' + guest_uuid.__str__())
        show_area = 'all'

    if sequence is not None:
        filters.append('sequence:in:' + sequence.__str__())
        show_area = 'all'

    if show_area in ['unmount', 'data_disk', 'all']:
        if show_area == 'unmount':
            filters.append('sequence:eq:-1')

        elif show_area == 'data_disk':
            filters.append('sequence:gt:0')

        else:
            pass

    else:
        # 与前端页面相照应，首次打开时，默认只显示未挂载的磁盘
        filters.append('sequence:eq:-1')

    if order_by is not None:
        args.append('order_by=' + order_by)

    if order is not None:
        args.append('order=' + order)

    if filters.__len__() > 0:
        args.append('filter=' + ','.join(filters))

    hosts_url = url_for('api_hosts.r_get_by_filter', _external=True)
    disks_url = url_for('api_disks.r_get_by_filter', _external=True)

    if keyword is not None:
        disks_url = url_for('api_disks.r_content_search', _external=True)
        # 关键字检索，不支持显示域过滤
        show_area = 'all'

    hosts_ret = requests.get(url=hosts_url, cookies=request.cookies)
    hosts_ret = json.loads(hosts_ret.content)

    hosts_mapping_by_node_id = dict()
    for host in hosts_ret['data']:
        hosts_mapping_by_node_id[int(host['node_id'])] = host

    if args.__len__() > 0:
        disks_url = disks_url + '?' + '&'.join(args)

    disks_ret = requests.get(url=disks_url, cookies=request.cookies)
    disks_ret = json.loads(disks_ret.content)

    guests_uuid = list()
    disks_uuid = list()

    for disk in disks_ret['data']:
        disks_uuid.append(disk['uuid'])

        if disk['guest_uuid'].__len__() == 36:
            guests_uuid.append(disk['guest_uuid'])

    if guests_uuid.__len__() > 0:
        guests, _ = Guest.get_by_filter(filter_str='uuid:in:' + ','.join(guests_uuid))

        guests_uuid_mapping = dict()
        for guest in guests:
            guests_uuid_mapping[guest['uuid']] = guest

        for i, disk in enumerate(disks_ret['data']):
            if disk['guest_uuid'].__len__() == 36:
                disks_ret['data'][i]['guest'] = guests_uuid_mapping[disk['guest_uuid']]

    if disks_uuid.__len__() > 0:
        snapshots_id_mapping_by_disks_uuid_url = url_for('api_snapshots.r_get_snapshots_by_disks_uuid',
                                                         disks_uuid=','.join(disks_uuid), _external=True)
        snapshots_id_mapping_by_disks_uuid_ret = requests.get(url=snapshots_id_mapping_by_disks_uuid_url,
                                                              cookies=request.cookies)
        snapshots_id_mapping_by_disks_uuid_ret = json.loads(snapshots_id_mapping_by_disks_uuid_ret.content)

        snapshots_id_mapping_by_disk_uuid = dict()

        for snapshot_id_mapping_by_disk_uuid in snapshots_id_mapping_by_disks_uuid_ret['data']:

            disk_uuid = snapshot_id_mapping_by_disk_uuid['disk_uuid']
            snapshot_id = snapshot_id_mapping_by_disk_uuid['snapshot_id']

            if disk_uuid not in snapshots_id_mapping_by_disk_uuid:
                snapshots_id_mapping_by_disk_uuid[disk_uuid] = list()

            snapshots_id_mapping_by_disk_uuid[disk_uuid].append(snapshot_id)

        for i, disk in enumerate(disks_ret['data']):
            if disk['uuid'] in snapshots_id_mapping_by_disk_uuid:
                disks_ret['data'][i]['snapshot'] = snapshots_id_mapping_by_disk_uuid[disk['uuid']]

    config = Config()
    config.id = 1
    config.get()

    show_on_host = False
    if config.storage_mode == StorageMode.local.value:
        show_on_host = True

    last_page = int(ceil(disks_ret['paging']['total'] / float(page_size)))
    page_length = 5
    pages = list()
    if page < int(ceil(page_length / 2.0)):
        for i in range(1, page_length + 1):
            pages.append(i)
            if i == last_page or last_page == 0:
                break

    elif last_page - page < page_length / 2:
        for i in range(last_page - page_length + 1, last_page + 1):
            if i < 1:
                continue
            pages.append(i)

    else:
        for i in range(page - page_length / 2, page + int(ceil(page_length / 2.0))):
            pages.append(i)
            if i == last_page or last_page == 0:
                break

    ret = dict()
    ret['state'] = ji.Common.exchange_state(20000)

    ret['data'] = {
        'disks': disks_ret['data'],
        'hosts_mapping_by_node_id': hosts_mapping_by_node_id,
        'order_by': order_by,
        'order': order,
        'show_area': show_area,
        'config': config.__dict__,
        'show_on_host': show_on_host,
        'paging': disks_ret['paging'],
        'page': page,
        'page_size': page_size,
        'keyword': keyword,
        'pages': pages,
        'last_page': last_page
    }

    return ret


@Utils.dumps2response
def r_detail(uuid):
    disk = Disk()
    disk.uuid = uuid
    disk.get_by(field='uuid')
    disk.wrap_device(dev_table=dev_table)

    guest = None
    os_template_image = None

    config = Config()
    config.id = 1
    config.get()

    if disk.sequence != -1:
        guest = Guest()
        guest.uuid = disk.guest_uuid
        guest.get_by('uuid')

        os_template_image = OSTemplateImage()
        os_template_image.id = guest.os_template_image_id
        os_template_image.get()

        guest = guest.__dict__
        os_template_image = os_template_image.__dict__

    ret = dict()
    ret['state'] = ji.Common.exchange_state(20000)

    ret['data'] = {
        'guest': guest,
        'os_template_image': os_template_image,
        'disk': disk.__dict__,
        'config': config.__dict__
    }

    return ret


