import time

from orm.common.orm_common.injector import injector
from orm.services.image_manager.ims.logger import get_logger
from orm.services.image_manager.ims.logic.error_base import ErrorStatus, NotFoundError
from orm.services.image_manager.ims.persistency.sql_alchemy.db_models import ImageCustomer, ImageRegion
from orm.services.image_manager.ims.persistency.wsme.models import (ImageSummary, ImageSummaryResponse,
                                                                    ImageWrapper, RegionWrapper)
from orm.services.image_manager.ims.utils import utils as ImsUtils

LOG = get_logger(__name__)

di = injector.get_di()


@di.dependsOn('data_manager')
def create_image(image_wrapper, image_uuid, transaction_id):
    DataManager = di.resolver.unpack(create_image)
    datamanager = DataManager()

    image_wrapper.image.id = image_uuid

    image_wrapper.image.created_at = str(int(time.time()))
    image_wrapper.image.updated_at = image_wrapper.image.created_at

    try:
        image_wrapper.handle_region_group()
        image_wrapper.validate_model()
        sql_image = image_wrapper.to_db_model()

        image_rec = datamanager.get_record('image')

        datamanager.begin_transaction()
        image_rec.insert(sql_image)
        datamanager.flush()  # i want to get any exception created by this
        # insert
        existing_region_names = []
        send_to_rds_if_needed(sql_image, existing_region_names, "post",
                              transaction_id)

        datamanager.commit()

        ret_image = get_image_by_uuid(image_uuid)
        return ret_image

    except Exception as exp:
        LOG.log_exception("ImageLogic - Failed to CreateImage", exp)
        datamanager.rollback()
        raise


@di.dependsOn('rds_proxy')
def send_to_rds_if_needed(sql_image, existing_region_names, http_action,
                          transaction_id):
    rds_proxy = di.resolver.unpack(send_to_rds_if_needed)
    if (sql_image.regions and len(sql_image.regions) > 0) or len(
            existing_region_names) > 0:
        image_dict = sql_image.get_proxy_dict()
        update_region_actions(image_dict, existing_region_names, http_action)
        if image_dict['regions'] or len(existing_region_names) > 0:
            LOG.debug("Image is valid to send to RDS - sending to RDS Proxy ")
            rds_proxy.send_image(image_dict, transaction_id, http_action)
        else:
            LOG.debug("Group with no regions - wasn't send to RDS Proxy " + str(
                sql_image))
    else:
        LOG.debug("Image with no regions - wasn't send to RDS Proxy " + str(
            sql_image))


@di.dependsOn('data_manager')
def update_image(image_wrapper, image_uuid, transaction_id, http_action="put"):
    DataManager = di.resolver.unpack(update_image)
    datamanager = DataManager()

    try:
        image_wrapper.validate_model('update')
        new_image = image_wrapper.to_db_model()
        new_image.id = image_uuid

        image_rec = datamanager.get_record('image')
        sql_image = image_rec.get_image_by_id(image_uuid)

        if sql_image is None:
            raise NotFoundError(status_code=404,
                                message="Image {0} does not exist for update".format(
                                    image_uuid))

        image_wrapper.validate_update(sql_image, new_image)

        datamanager.begin_transaction()

        new_image.owner = sql_image.owner
        existing_regions = sql_image.get_existing_region_names()
        new_image.created_at = int(sql_image.created_at)
        new_image.updated_at = int(time.time())
        # result = image_rec.delete_image_by_id(image_uuid)
        datamanager.get_session().delete(sql_image)
        # del sql_image
        image_rec.insert(new_image)
        datamanager.flush()

        send_to_rds_if_needed(new_image, existing_regions, http_action,
                              transaction_id)

        datamanager.commit()

        ret_image = get_image_by_uuid(image_uuid)

        return ret_image

    except Exception as exp:
        datamanager.rollback()
        LOG.log_exception("ImageLogic - Failed to update image", exp)
        raise


@di.dependsOn('rds_proxy')
@di.dependsOn('data_manager')
def delete_image_by_uuid(image_uuid, transaction_id):
    rds_proxy, DataManager = di.resolver.unpack(delete_image_by_uuid)
    datamanager = DataManager()

    try:

        datamanager.begin_transaction()
        image_rec = datamanager.get_record('image')

        sql_image = image_rec.get_image_by_id(image_uuid)
        if sql_image is None:
            raise NotFoundError(message="Image '{}' not found".format(image_uuid))

        image_existing_region_names = sql_image.get_existing_region_names()
        if len(image_existing_region_names) > 0:
            # Do not delete a flavor that still has some regions
            raise ErrorStatus(405,
                              "Cannot delete a image with regions. "
                              "Please delete the regions first and then "
                              "delete the image. ")

        # Get status from RDS
        image_status = rds_proxy.get_status(sql_image.id, False)

        status_resp = None

        if image_status.status_code == 200:
            status_resp = image_status.json()['status']
            LOG.debug('RDS returned status: {}'.format(status_resp))

        elif image_status.status_code == 404:
            status_resp = 'Success'

        else:
            # fail to get status from rds
            raise ErrorStatus(500, "fail to get status for this resource "
                                   "deleting image not allowed ")

        if status_resp != 'Success':
            raise ErrorStatus(405, "not allowed as aggregate status "
                                   "have to be Success (either the deletion"
                                   " failed on one of the regions or it is "
                                   "still in progress)")

        image_rec.delete_image_by_id(image_uuid)
        datamanager.flush()  # i want to get any exception created by this

        # delete
        datamanager.commit()

    except Exception as exp:
        LOG.log_exception("ImageLogic - Failed to delete image", exp)
        datamanager.rollback()
        raise


@di.dependsOn('data_manager')
def add_regions(image_uuid, regions, transaction_id):
    DataManager = di.resolver.unpack(add_regions)
    datamanager = DataManager()

    try:
        image_rec = datamanager.get_record('image')
        sql_image = image_rec.get_image_by_id(image_uuid)
        if not sql_image:
            raise ErrorStatus(404, 'image with id: {0} not found'.format(
                image_uuid))

        existing_region_names = sql_image.get_existing_region_names()

        for region in regions.regions:
            db_region = ImageRegion(region_name=region.name, region_type=region.type)
            sql_image.add_region(db_region)

        datamanager.flush()  # i want to get any exception created by
        # previous actions against the database

        send_to_rds_if_needed(sql_image, existing_region_names, "put",
                              transaction_id)

        datamanager.commit()

        image_wrapper = get_image_by_uuid(image_uuid)
        ret = RegionWrapper(regions=image_wrapper.image.regions)
        return ret

    except ErrorStatus as exp:
        LOG.log_exception("ImageLogic - Failed to add regions", exp)
        datamanager.rollback()
        raise exp
    except Exception as exp:
        LOG.log_exception("ImageLogic - Failed to add regions", exp)
        datamanager.rollback()
        raise exp


@di.dependsOn('data_manager')
def replace_regions(image_uuid, regions, transaction_id):
    DataManager = di.resolver.unpack(replace_regions)
    datamanager = DataManager()

    try:
        image_rec = datamanager.get_record('image')
        sql_image = image_rec.get_image_by_id(image_uuid)
        if not sql_image:
            raise ErrorStatus(404, 'image with id: {0} not found'.format(
                image_uuid))

        existing_region_names = sql_image.get_existing_region_names()

        sql_image.remove_all_regions()
        datamanager.flush()

        for region in regions.regions:
            db_region = ImageRegion(region_name=region.name, region_type=region.type)
            sql_image.add_region(db_region)
        datamanager.flush()  # i want to get any exception created by
        # previous actions against the database

        send_to_rds_if_needed(sql_image, existing_region_names, "put",
                              transaction_id)

        datamanager.commit()

        image_wrapper = get_image_by_uuid(image_uuid)
        ret = RegionWrapper(regions=image_wrapper.image.regions)
        return ret

    except ErrorStatus as exp:
        LOG.log_exception("ImageLogic - Failed to replace regions", exp)
        datamanager.rollback()
        raise exp
    except Exception as exp:
        LOG.log_exception("ImageLogic - Failed to repalce regions", exp)
        datamanager.rollback()
        raise exp


@di.dependsOn('data_manager')
def delete_region(image_uuid, region_name, transaction_id, on_success_by_rds,
                  force_delete):
    DataManager = di.resolver.unpack(delete_region)
    datamanager = DataManager()

    try:
        image_rec = datamanager.get_record('image')
        sql_image = image_rec.get_image_by_id(image_uuid)
        if on_success_by_rds and not sql_image:
            return
        if not sql_image:
            raise ErrorStatus(404, 'image with id: {0} not found'.format(
                              image_uuid))
        # do not allow delete_region for protected images
        if sql_image.protected:
            protect_msg = "Protected image {} cannot be deleted. Please " \
                          "update image with protected=false and try again"
            raise ErrorStatus(400, protect_msg.format(image_uuid))

        existing_region_names = sql_image.get_existing_region_names()
        sql_image.remove_region(region_name)

        datamanager.flush()  # Get any exception created by
        # previous actions against the database
        if on_success_by_rds:
            datamanager.commit()
        else:
            send_to_rds_if_needed(sql_image, existing_region_names, "put",
                                  transaction_id)
            if force_delete:
                datamanager.commit()
            else:
                datamanager.rollback()

    except ErrorStatus as exp:
        LOG.log_exception("ImageLogic - Failed to update image", exp)
        datamanager.rollback()
        raise

    except Exception as exp:
        LOG.log_exception("ImageLogic - Failed to delete region", exp)
        datamanager.rollback()
        raise

    finally:
        datamanager.close()


@di.dependsOn('data_manager')
def add_customers(image_uuid, customers, transaction_id):
    DataManager = di.resolver.unpack(add_customers)
    datamanager = DataManager()

    try:
        image_rec = datamanager.get_record('image')
        sql_image = image_rec.get_image_by_id(image_uuid)
        if not sql_image:
            raise ErrorStatus(404, 'image with id: {0} not found'.format(
                image_uuid))

        if sql_image.visibility == "public":
            raise ErrorStatus(400, 'Cannot add Customers to public Image')

        existing_region_names = sql_image.get_existing_region_names()

        for user in customers.customers:
            db_Customer = ImageCustomer(customer_id=user)
            sql_image.add_customer(db_Customer)

        datamanager.flush()  # i want to get any exception created by
        # previous actions against the database
        send_to_rds_if_needed(sql_image, existing_region_names, "put",
                              transaction_id)
        datamanager.commit()

        ret_image = get_image_by_uuid(image_uuid)
        return ret_image

    except Exception as exp:
        if 'conflicts with persistent instance' in exp.message or 'Duplicate entry' in exp.message:
            raise ErrorStatus(409, "Duplicate Customer for Image")
        LOG.log_exception("ImageLogic - Failed to add Customers", exp)
        datamanager.rollback()
        raise


@di.dependsOn('data_manager')
def replace_customers(image_uuid, customers, transaction_id):
    DataManager = di.resolver.unpack(replace_customers)
    datamanager = DataManager()

    try:
        image_rec = datamanager.get_record('image')
        sql_image = image_rec.get_image_by_id(image_uuid)
        if not sql_image:
            raise ErrorStatus(404, 'image {0} not found'.format(image_uuid))

        if sql_image.visibility == "public":
            raise ValueError('Cannot add Customers to public Image')

        existing_region_names = sql_image.get_existing_region_names()
        sql_image.remove_all_customers()
        datamanager.flush()

        for cust in customers.customers:
            db_Customer = ImageCustomer(customer_id=cust)
            sql_image.add_customer(db_Customer)

        datamanager.flush()  # get exception created by previous db actions
        send_to_rds_if_needed(sql_image, existing_region_names, "put",
                              transaction_id)
        datamanager.commit()

        ret_image = get_image_by_uuid(image_uuid)
        return ret_image

    except Exception as exp:
        if 'conflicts with persistent instance' in exp.message or 'Duplicate entry' in exp.message:
            raise ErrorStatus(409, "Duplicate Customer for Image")
        LOG.log_exception("ImageLogic - Failed to add Customers", exp)
        datamanager.rollback()
        raise


@di.dependsOn('data_manager')
def delete_customer(image_uuid, customer_id, transaction_id):
    DataManager = di.resolver.unpack(delete_customer)
    datamanager = DataManager()

    try:
        image_rec = datamanager.get_record('image')
        sql_image = image_rec.get_image_by_id(image_uuid)
        if not sql_image:
            raise ErrorStatus(404, 'image {0} not found'.format(image_uuid))
        # if trying to delete the only one Customer then return value error
        if sql_image.visibility == "public":
            raise ValueError("Image {} is public, no customers".format(image_uuid))

        if len(sql_image.customers) == 1 and \
                sql_image.customers[0].customer_id == customer_id:
            raise ValueError('Private Image must have at least one Customer - '
                             'You are trying to delete the only one Customer')

        existing_region_names = sql_image.get_existing_region_names()
        sql_image.remove_customer(customer_id)

        datamanager.flush()  # i want to get any exception created by
        # previous actions against the database
        send_to_rds_if_needed(sql_image, existing_region_names, "put",
                              transaction_id)
        datamanager.commit()

    except Exception as exp:
        LOG.log_exception("ImageLogic - Failed to delete Customer", exp)
        datamanager.rollback()
        raise


@di.dependsOn('data_manager')
@di.dependsOn('rds_proxy')
def get_image_by_uuid(image_uuid, query_by_id_or_name=False):
    """This function includes an optional boolean parameter "query_by_id_or_name".
      If query_by_id_or_name evaluates to true, IMS logic will fetch the image
      record whose "image_uuid" parameter value matches either the image id or name value.
      Otherwise it defaults to query by image ID value only.
    """
    DataManager, rds_proxy = di.resolver.unpack(get_image_by_uuid)
    datamanager = DataManager()

    LOG.debug("Get image by uuid : {}".format(image_uuid))
    try:

        datamanager.begin_transaction()
        image_rec = datamanager.get_record('image')

        # Only the get_image API will pass the optional parameter and set it to true
        if query_by_id_or_name:
            sql_image = image_rec.get_image(image_uuid)
        else:
            # all other image APIs will NOT pass the optional parameter
            sql_image = image_rec.get_image_by_id(image_uuid)
        if not sql_image:
            raise NotFoundError(status_code=404,
                                message="Image {0} not found ".format(
                                    image_uuid))

        image_wrapper = ImageWrapper.from_db_model(sql_image)

        # get image own link
        image_wrapper.image.links, image_wrapper.image.self_link = ImsUtils.get_server_links(image_uuid)
        # convert time stamp format to human readable time
        image_wrapper.image.created_at = ImsUtils.convert_time_human(
            image_wrapper.image.created_at)
        image_wrapper.image.updated_at = ImsUtils.convert_time_human(
            image_wrapper.image.updated_at)
        # Get the status from RDS
        image_status = rds_proxy.get_status(image_wrapper.image.id, False)

        if image_status.status_code == 404:
            # image not on rds resource table - applicable to images created with no region assigned
            image_wrapper.image.status = 'no regions'

        elif image_status.status_code == 200:
            image_status = image_status.json()
            if image_wrapper.image.regions:
                image_wrapper.image.status = image_status['status']
            else:
                image_wrapper.image.status = 'no regions'

            # update status for all regions
            for result_regions in image_wrapper.image.regions:
                for status_region in image_status['regions']:
                    if result_regions.name == status_region['region']:
                        result_regions.status = status_region['status']
                        if status_region['error_msg']:
                            result_regions.set_error_message(status_region['error_msg'])

        # status codes not falling under 404 (not found) or 200 (success)
        else:
            raise ErrorStatus(500, "unsuccessful GET - failed to get status for this resource")

    except NotFoundError as exp:
        datamanager.rollback()
        LOG.log_exception("ImageLogic - Failed to update image", exp)
        raise exp

    except Exception as exp:
        datamanager.rollback()
        LOG.log_exception("ImageLogic - Failed to delete Customer", exp)
        raise

    return image_wrapper


@di.dependsOn('data_manager')
@di.dependsOn('rds_proxy')
def get_image_list_by_params(visibility, region, Customer):
    DataManager, rds_proxy = di.resolver.unpack(get_image_list_by_params)

    datamanager = DataManager()
    try:
        image_record = datamanager.get_record('image')
        sql_images = image_record.get_images_by_criteria(visibility=visibility,
                                                         region=region,
                                                         Customer=Customer)

        response = ImageSummaryResponse()
        if sql_images:
            uuids = ','.join(str("\'" + sql_image.id + "\'")
                             for sql_image in sql_images if sql_image and sql_image.id)
            resource_status_dict = image_record.get_images_status_by_uuids(uuids)

            for sql_image in sql_images:
                image = ImageSummary.from_db_model(sql_image)
                if sql_image.id:
                    # rds_region_list contains tuples - each containing the regions associated with the image
                    # along with the region status
                    rds_region_list = resource_status_dict.get(sql_image.id)

                    if rds_region_list and image.regions:
                        # set image.status to 'error' if any of the regions has an 'Error' status'
                        # else, if any region status shows 'Submitted' then set image status to 'Pending'
                        # otherwise image status is 'Success'
                        error_status = [item for item in rds_region_list if item[1] == 'Error']
                        submitted_status = [item for item in rds_region_list if item[1] == 'Submitted']
                        success_status = [item for item in rds_region_list if item[1] == 'Success']

                        if len(error_status) > 0:
                            image.status = 'Error'
                        elif len(submitted_status) > 0:
                            image.status = 'Pending'
                        elif len(success_status) > 0:
                            image.status = 'Success'
                    else:
                        image.status = 'no regions'

                response.images.append(image)
        return response

    except ErrorStatus as exp:
        LOG.log_exception("ImageLogic - Failed to get list", exp)
        raise
    except Exception as exp:
        LOG.log_exception("ImageLogic - Failed to get list", exp)
        raise


def update_region_actions(image_dict, existing_region_names, action="put"):
    if action == "delete":
        set_regions_action(image_dict, "delete")
    elif action == "post":
        set_regions_action(image_dict, "create")
    else:  # put
        for region in image_dict["regions"]:
            if region["name"] in existing_region_names:
                region["action"] = "modify"
            else:
                region["action"] = "create"

        # add deleted regions
        for exist_region_name in existing_region_names:
            if region_name_exist_in_regions(exist_region_name,
                                            image_dict["regions"]):
                continue
            else:
                image_dict["regions"].append(
                    {"name": exist_region_name, "action": "delete"})


def region_name_exist_in_regions(region_name, regions):
    for region in regions:
        if region["name"] == region_name:
            return True
    return False


def set_regions_action(image_dict, action):
    for region in image_dict["regions"]:
        region["action"] = action


@di.dependsOn('data_manager')
def enable_image(image_uuid, int_enabled, transaction_id):
    DataManager = di.resolver.unpack(enable_image)
    datamanager = DataManager()

    try:
        image_rec = datamanager.get_record('image')
        sql_image = image_rec.get_image_by_id(image_uuid)
        if not sql_image:
            raise ErrorStatus(404, 'Image with id: {0} not found'.format(
                image_uuid))

        sql_image.enabled = int_enabled

        existing_region_names = sql_image.get_existing_region_names()

        datamanager.flush()  # i want to get any exception created by this
        # insert method

        send_to_rds_if_needed(sql_image, existing_region_names, "put",
                              transaction_id)

        datamanager.commit()

        ret_image = get_image_by_uuid(image_uuid)
        return ret_image

    except ErrorStatus as exp:
        LOG.log_exception("ImageLogic - Failed to change image activation value", exp)
        datamanager.rollback()
        raise exp
    except Exception as exp:
        LOG.log_exception("ImageLogic - Failed to change image activation value", exp)
        datamanager.rollback()
        raise exp
