#    Copyright 2014 Mirantis, 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.

from itertools import chain
import re

from alembic import op
from oslo.serialization import jsonutils
import six
import sqlalchemy as sa
from sqlalchemy.sql import text

from nailgun import consts
from nailgun.settings import settings


def upgrade_enum(table, column_name, enum_name, old_options, new_options):
    old_type = sa.Enum(*old_options, name=enum_name)
    new_type = sa.Enum(*new_options, name=enum_name)
    tmp_type = sa.Enum(*new_options, name="_" + enum_name)
    # Create a temporary type, convert and drop the "old" type
    tmp_type.create(op.get_bind(), checkfirst=False)
    op.execute(
        u'ALTER TABLE {0} ALTER COLUMN {1} TYPE _{2}'
        u' USING {1}::text::_{2}'.format(
            table,
            column_name,
            enum_name
        )
    )
    old_type.drop(op.get_bind(), checkfirst=False)
    # Create and convert to the "new" type
    new_type.create(op.get_bind(), checkfirst=False)
    op.execute(
        u'ALTER TABLE {0} ALTER COLUMN {1} TYPE {2}'
        u' USING {1}::text::{2}'.format(
            table,
            column_name,
            enum_name
        )
    )
    tmp_type.drop(op.get_bind(), checkfirst=False)


def drop_enum(name):
    op.execute(
        u'DROP TYPE {0}'.format(name)
    )


def convert_condition_value(val):
    if isinstance(val, six.string_types):
        return "'{0}'".format(val)
    return str(val).lower()


def negate_condition(condition):
    """Negates condition.
    """
    return "not ({0})".format(condition)


def remove_question_operator(expression):
    """Removes '?' operator from expressions, it was deprecated in 6.0
    """
    return re.sub(r'(:[\w\.\-]+)\?', '\\1', expression)


def upgrade_release_attributes_50_to_51(attrs_meta):
    if not attrs_meta.get('editable'):
        return attrs_meta

    def depends_to_restrictions(depends, restrictions):
        for cond in depends:
            expr = cond.keys()[0]
            restrictions.append(
                expr + " != " + convert_condition_value(cond[expr]))

    def conflicts_to_restrictions(conflicts, restrictions):
        for cond in conflicts:
            expr = cond.keys()[0]
            restrictions.append(
                expr + " == " + convert_condition_value(cond[expr]))

    for _, group in six.iteritems(attrs_meta.get('editable')):
        for _, attr in six.iteritems(group):
            restrictions = []
            if attr.get('depends'):
                depends_to_restrictions(attr['depends'], restrictions)
                attr.pop('depends')
            if attr.get('conflicts'):
                conflicts_to_restrictions(attr['conflicts'], restrictions)
                attr.pop('conflicts')
            if restrictions:
                attr['restrictions'] = restrictions
    return attrs_meta


def upgrade_release_attributes_51_to_60(attrs_meta):
    """Remove '?' operator from expressions
    """
    if not attrs_meta.get('editable'):
        return attrs_meta

    def convert_restrictions(restrictions):
        result = []
        for restriction in restrictions:
            if isinstance(restriction, basestring):
                restriction = remove_question_operator(restriction)
            else:
                restriction['condition'] = remove_question_operator(
                    restriction['condition'])
            result.append(restriction)
        return result

    for _, group in six.iteritems(attrs_meta.get('editable')):
        for _, attr in six.iteritems(group):
            if 'restrictions' in attr:
                attr['restrictions'] = convert_restrictions(
                    attr['restrictions'])
            if 'values' in attr:
                for value in attr['values']:
                    if 'restrictions' in value:
                        value['restrictions'] = convert_restrictions(
                            value['restrictions'])

    return attrs_meta


def upgrade_release_roles_50_to_51(roles_meta):
    for _, role in six.iteritems(roles_meta):
        if role.get('depends'):
            for depend in role['depends']:
                cond = depend.get('condition')
                if isinstance(cond, dict):
                    expr = cond.keys()[0]
                    depend['condition'] = \
                        expr + " == " + convert_condition_value(cond[expr])
    return roles_meta


def upgrade_release_roles_51_to_60(roles_meta, add_meta=None):
    """Convert all role_metadata.depends values into
    roles_metadata.restrictions.
    """
    add_meta = add_meta or {}
    for role_name, role in six.iteritems(roles_meta):
        for depend in role.get('depends', []):
            cond = depend.get('condition')
            new_restriction = {
                'condition': remove_question_operator(negate_condition(cond))
            }
            if 'warning' in depend:
                new_restriction['message'] = depend['warning']

            role.setdefault('restrictions', [])
            role['restrictions'].append(new_restriction)

        if 'depends' in role:
            del role['depends']

        if role_name in add_meta:
            role.update(add_meta[role_name])
    return roles_meta


def upgrade_clusters_replaced_info(connection):
    select = text(
        """SELECT id, replaced_provisioning_info, replaced_deployment_info
        FROM clusters""")
    clusters = connection.execute(select)
    for cluster in clusters:
        nodes_select = text(
            """SELECT id FROM nodes WHERE cluster_id=:id""")
        nodes = connection.execute(
            nodes_select,