##############################################################################
# Copyright (c) 2017 Huawei Technologies Co.,Ltd.
#
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Apache License, Version 2.0
# which accompanies this distribution, and is available at
# http://www.apache.org/licenses/LICENSE-2.0
##############################################################################
import logging
import os
import uuid
import threading
import requests
import datetime

from api import ApiResource
from api.database.v2.handlers import V2ImageHandler
from api.database.v2.handlers import V2EnvironmentHandler
from yardstick.common.utils import result_handler
from yardstick.common.utils import source_env
from yardstick.common import openstack_utils
from yardstick.common.openstack_utils import get_glance_client
from yardstick.common import constants as consts

LOG = logging.getLogger(__name__)
LOG.setLevel(logging.DEBUG)

IMAGE_MAP = {
    'yardstick-image': {
        'path': os.path.join(consts.IMAGE_DIR, 'yardstick-image.img'),
        'url': 'http://artifacts.opnfv.org/yardstick/images/yardstick-image.img'
    },
    'Ubuntu-16.04': {
        'path': os.path.join(consts.IMAGE_DIR, 'xenial-server-cloudimg-amd64-disk1.img'),
        'url': 'cloud-images.ubuntu.com/xenial/current/xenial-server-cloudimg-amd64-disk1.img'
    },
    'cirros-0.3.5': {
        'path': os.path.join(consts.IMAGE_DIR, 'cirros-0.3.5-x86_64-disk.img'),
        'url': 'http://download.cirros-cloud.net/0.3.5/cirros-0.3.5-x86_64-disk.img'
    }
}


class V2Images(ApiResource):

    def get(self):
        try:
            source_env(consts.OPENRC)
        except OSError:
            return result_handler(consts.API_ERROR, 'source openrc error')

        image_list = openstack_utils.list_images()

        if image_list is False:
            return result_handler(consts.API_ERROR, 'get images error')

        images = {i.name: format_image_info(i) for i in image_list}

        return result_handler(consts.API_SUCCESS, {'status': 1, 'images': images})

    def post(self):
        return self._dispatch_post()

    def load_image(self, args):
        try:
            image_name = args['name']
        except KeyError:
            return result_handler(consts.API_ERROR, 'image name must provided')

        if image_name not in IMAGE_MAP:
            return result_handler(consts.API_ERROR, 'wrong image name')

        thread = threading.Thread(target=self._do_load_image, args=(image_name,))
        thread.start()
        return result_handler(consts.API_SUCCESS, {'image': image_name})

    def upload_image(self, args):
        try:
            image_file = args['file']
        except KeyError:
            return result_handler(consts.API_ERROR, 'file must be provided')

        try:
            environment_id = args['environment_id']
        except KeyError:
            return result_handler(consts.API_ERROR, 'environment_id must be provided')

        try:
            uuid.UUID(environment_id)
        except ValueError:
            return result_handler(consts.API_ERROR, 'invalid environment id')

        environment_handler = V2EnvironmentHandler()
        try:
            environment = environment_handler.get_by_uuid(environment_id)
        except ValueError:
            return result_handler(consts.API_ERROR, 'no such environment')

        file_path = os.path.join(consts.IMAGE_DIR, image_file.filename)
        LOG.info('saving file')
        image_file.save(file_path)

        LOG.info('loading image')
        self._load_image(image_file.filename, file_path)

        LOG.info('creating image in DB')
        image_handler = V2ImageHandler()
        image_id = str(uuid.uuid4())
        image_init_data = {
            'uuid': image_id,
            'name': image_file.filename,
            'environment_id': environment_id
        }
        image_handler.insert(image_init_data)

        LOG.info('update image in environment')
        if environment.image_id:
            image_list = environment.image_id.split(',')
            image_list.append(image_id)
            new_image_id = ','.join(image_list)
        else:
            new_image_id = image_id

        environment_handler.update_attr(environment_id, {'image_id': new_image_id})

        return result_handler(consts.API_SUCCESS, {'uuid': image_id})

    def upload_image_by_url(self, args):
        try:
            url = args['url']
        except KeyError:
            return result_handler(consts.API_ERROR, 'url must be provided')

        try:
            environment_id = args['environment_id']
        except KeyError:
            return result_handler(consts.API_ERROR, 'environment_id must be provided')

        try:
            uuid.UUID(environment_id)
        except ValueError:
            return result_handler(consts.API_ERROR, 'invalid environment id')

        environment_handler = V2EnvironmentHandler()
        try:
            environment = environment_handler.get_by_uuid(environment_id)
        except ValueError:
            return result_handler(consts.API_ERROR, 'no such environment')

        thread = threading.Thread(target=self._do_upload_image_by_url, args=(url,))
        thread.start()

        file_name = url.split('/')[-1]

        LOG.info('creating image in DB')
        image_handler = V2ImageHandler()
        image_id = str(uuid.uuid4())
        image_init_data = {
            'uuid': image_id,
            'name': file_name,
            'environment_id': environment_id
        }
        image_handler.insert(image_init_data)

        LOG.info('update image in environment')
        if environment.image_id:
            image_list = environment.image_id.split(',')
            image_list.append(image_id)
            new_image_id = ','.join(image_list)
        else:
            new_image_id = image_id

        environment_handler.update_attr(environment_id, {'image_id': new_image_id})

        return result_handler(consts.API_SUCCESS, {'uuid': image_id})

    def delete_image(self, args):
        try:
            image_name = args['name']
        except KeyError:
            return result_handler(consts.API_ERROR, 'image name must provided')

        if image_name not in IMAGE_MAP:
            return result_handler(consts.API_ERROR, 'wrong image name')

        glance_client = get_glance_client()
        try:
            image = next((i for i in glance_client.images.list() if i.name == image_name))
        except StopIteration:
            return result_handler(consts.API_ERROR, 'can not find image')

        glance_client.images.delete(image.id)

        return result_handler(consts.API_SUCCESS, {})

    def _do_upload_image_by_url(self, url):
        file_name = url.split('/')[-1]
        path = os.path.join(consts.IMAGE_DIR, file_name)

        LOG.info('download image')
        self._download_image(url, path)

        LOG.info('loading image')
        self._load_image(file_name, path)

    def _do_load_image(self, image_name):
        if not os.path.exists(IMAGE_MAP[image_name]['path']):
            self._download_image(IMAGE_MAP[image_name]['url'],
                                 IMAGE_MAP[image_name]['path'])

        self._load_image(image_name, IMAGE_MAP[image_name]['path'])

    def _load_image(self, image_name, image_path):
        LOG.info('source openrc')
        source_env(consts.OPENRC)

        LOG.info('load image')
        glance_client = get_glance_client()
        image = glance_client.images.create(name=image_name,
                                            visibility='public',
                                            disk_format='qcow2',
                                            container_format='bare')
        with open(image_path, 'rb') as f:
            glance_client.images.upload(image.id, f)

        LOG.info('Done')

    def _download_image(self, url, path):
        start = datetime.datetime.now().replace(microsecond=0)

        LOG.info('download image from: %s', url)
        self._download_file(url, path)

        end = datetime.datetime.now().replace(microsecond=0)
        LOG.info('download image success, total: %s s', end - start)

    def _download_handler(self, start, end, url, filename):

        headers = {'Range': 'bytes=%d-%d' % (start, end)}
        r = requests.get(url, headers=headers, stream=True)

        with open(filename, "r+b") as fp:
            fp.seek(start)
            fp.tell()
            fp.write(r.content)

    def _download_file(self, url, path, num_thread=5):

        r = requests.head(url)
        try:
            file_size = int(r.headers['content-length'])
        except (TypeError, ValueError):
            return

        with open(path, 'wb') as f:
            f.truncate(file_size)

        thread_list = []
        part = file_size // num_thread
        for i in range(num_thread):
            start = part * i
            end = start + part if i != num_thread - 1 else file_size

            kwargs = {'start': start, 'end': end, 'url': url, 'filename': path}
            t = threading.Thread(target=self._download_handler, kwargs=kwargs)
            t.setDaemon(True)
            t.start()
            thread_list.append(t)

        for t in thread_list:
            t.join()


class V2Image(ApiResource):
    def get(self, image_id):
        try:
            uuid.UUID(image_id)
        except ValueError:
            return result_handler(consts.API_ERROR, 'invalid image id')

        image_handler = V2ImageHandler()
        try:
            image = image_handler.get_by_uuid(image_id)
        except ValueError:
            return result_handler(consts.API_ERROR, 'no such image id')

        images = openstack_utils.list_images()
        try:
            image = next((i for i in images if i.name == image.name))
        except StopIteration:
            pass

        return_image = format_image_info(image)
        return_image['id'] = image_id

        return result_handler(consts.API_SUCCESS, {'image': return_image})

    def delete(self, image_id):
        try:
            uuid.UUID(image_id)
        except ValueError:
            return result_handler(consts.API_ERROR, 'invalid image id')

        image_handler = V2ImageHandler()
        try:
            image = image_handler.get_by_uuid(image_id)
        except ValueError:
            return result_handler(consts.API_ERROR, 'no such image id')

        LOG.info('delete image in openstack')
        glance_client = get_glance_client()
        try:
            image_o = next((i for i in glance_client.images.list() if i.name == image.name))
        except StopIteration:
            return result_handler(consts.API_ERROR, 'can not find image')

        glance_client.images.delete(image_o.id)

        LOG.info('delete image in environment')
        environment_id = image.environment_id
        environment_handler = V2EnvironmentHandler()
        environment = environment_handler.get_by_uuid(environment_id)
        image_list = environment.image_id.split(',')
        image_list.remove(image_id)
        environment_handler.update_attr(environment_id, {'image_id': ','.join(image_list)})

        LOG.info('delete image in DB')
        image_handler.delete_by_uuid(image_id)

        return result_handler(consts.API_SUCCESS, {'image': image_id})


def format_image_info(image):
    image_dict = {}

    if image is None:
        return image_dict

    image_dict['name'] = image.name
    image_dict['size'] = float(image.size) / 1024 / 1024
    image_dict['status'] = image.status.upper()
    image_dict['time'] = image.updated_at

    return image_dict