# Copyright 2014 Rackspace
# All Rights Reserved.
#
#    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 datetime import datetime
import json

from oslo_log import log as logging

from trove.common import cfg
from trove.common import exception
from trove.common.exception import ModelNotFoundError
from trove.common.i18n import _
from trove.common import utils
from trove.datastore import models as dstore_models
from trove.db import get_db_api
from trove.db import models as dbmodels


CONF = cfg.CONF
LOG = logging.getLogger(__name__)


class Configurations(object):

    DEFAULT_LIMIT = CONF.configurations_page_size

    @staticmethod
    def load(context):
        if context is None:
            raise TypeError("Argument context not defined.")
        elif id is None:
            raise TypeError("Argument is not defined.")

        if context.is_admin:
            db_info = DBConfiguration.find_all(deleted=False)
            if db_info.count() == 0:
                LOG.debug("No configurations found for admin user")
        else:
            db_info = DBConfiguration.find_all(tenant_id=context.tenant,
                                               deleted=False)
            if db_info.count() == 0:
                LOG.debug("No configurations found for tenant %s"
                          % context.tenant)

        limit = int(context.limit or Configurations.DEFAULT_LIMIT)
        if limit > Configurations.DEFAULT_LIMIT:
            limit = Configurations.DEFAULT_LIMIT

        data_view = DBConfiguration.find_by_pagination('configurations',
                                                       db_info,
                                                       "foo",
                                                       limit=limit,
                                                       marker=context.marker)
        next_marker = data_view.next_page_marker
        return data_view.collection, next_marker


class Configuration(object):

    def __init__(self, context, configuration_id):
        self.context = context
        self.configuration_id = configuration_id

    @property
    def instances(self):
        return self.instances

    @property
    def items(self):
        return self.items

    @staticmethod
    def create(name, description, tenant_id, datastore, datastore_version):
        configurationGroup = DBConfiguration.create(
            name=name,
            description=description,
            tenant_id=tenant_id,
            datastore_version_id=datastore_version)
        return configurationGroup

    @staticmethod
    def create_items(cfg_id, values):
        LOG.debug("Saving configuration values for %s - "
                  "values: %s" % (cfg_id, values))
        config_items = []
        for key, val in values.iteritems():
            config_item = DBConfigurationParameter.create(
                configuration_id=cfg_id,
                configuration_key=key,
                configuration_value=val)
            config_items.append(config_item)
        return config_items

    @staticmethod
    def delete(context, group):
        deleted_at = datetime.utcnow()
        Configuration.remove_all_items(context, group.id, deleted_at)
        group.deleted = True
        group.deleted_at = deleted_at
        group.save()

    @staticmethod
    def remove_all_items(context, id, deleted_at):
        items = DBConfigurationParameter.find_all(configuration_id=id,
                                                  deleted=False).all()
        LOG.debug("Removing all configuration values for %s" % id)
        for item in items:
            item.deleted = True
            item.deleted_at = deleted_at
            item.save()

    @staticmethod
    def load_configuration_datastore_version(context, id):
        config = Configuration.load(context, id)
        datastore_version = dstore_models.DatastoreVersion.load_by_uuid(
            config.datastore_version_id)
        return datastore_version

    @staticmethod
    def load(context, id):
        try:
            if context.is_admin:
                return DBConfiguration.find_by(id=id, deleted=False)
            else:
                return DBConfiguration.find_by(id=id,
                                               tenant_id=context.tenant,
                                               deleted=False)
        except ModelNotFoundError:
            msg = _("Configuration group with ID %s could not be found.") % id
            raise ModelNotFoundError(msg)

    @staticmethod
    def find_parameter_details(name, detail_list):
        for item in detail_list:
            if item.name == name:
                return item
        return None

    @staticmethod
    def load_items(context, id):
        datastore_v = Configuration.load_configuration_datastore_version(
            context,
            id)
        config_items = DBConfigurationParameter.find_all(
            configuration_id=id, deleted=False).all()

        detail_list = DatastoreConfigurationParameters.load_parameters(
            datastore_v.id)

        for item in config_items:
            rule = Configuration.find_parameter_details(
                str(item.configuration_key), detail_list)
            if not rule:
                continue
            if rule.data_type == 'boolean':
                item.configuration_value = bool(int(item.configuration_value))
            elif rule.data_type == 'integer':
                item.configuration_value = int(item.configuration_value)
            else:
                item.configuration_value = str(item.configuration_value)
        return config_items

    def get_configuration_overrides(self):
        """Gets the overrides dictionary to apply to an instance."""
        overrides = {}
        if self.configuration_id:
            config_items = Configuration.load_items(self.context,
                                                    id=self.configuration_id)

            for i in config_items:
                overrides[i.configuration_key] = i.configuration_value
        return overrides

    def does_configuration_need_restart(self):
        datastore_v = Configuration.load_configuration_datastore_version(
            self.context,
            self.configuration_id)
        config_items = Configuration.load_items(self.context,
                                                id=self.configuration_id)
        LOG.debug("config_items: %s" % config_items)
        detail_list = DatastoreConfigurationParameters.load_parameters(
            datastore_v.id, show_deleted=True)

        for i in config_items:
            LOG.debug("config item: %s" % i)
            details = Configuration.find_parameter_details(
                i.configuration_key, detail_list)
            LOG.debug("parameter details: %s" % details)
            if not details:
                raise exception.NotFound(uuid=i.configuration_key)
            if bool(details.restart_required):
                return True
        return False

    @staticmethod
    def save(configuration, configuration_items):
        DBConfiguration.save(configuration)
        for item in configuration_items:
            item["deleted_at"] = None
            DBConfigurationParameter.save(item)


class DBConfiguration(dbmodels.DatabaseModelBase):
    _data_fields = ['name', 'description', 'tenant_id', 'datastore_version_id',
                    'deleted', 'deleted_at', 'created', 'updated']

    @property
    def datastore(self):
        datastore_version = dstore_models.DatastoreVersion.load_by_uuid(
            self.datastore_version_id)
        datastore = dstore_models.Datastore.load(
            datastore_version.datastore_id)
        return datastore

    @property
    def datastore_version(self):
        datastore_version = dstore_models.DatastoreVersion.load_by_uuid(
            self.datastore_version_id)
        return datastore_version


class DBConfigurationParameter(dbmodels.DatabaseModelBase):
    _data_fields = ['configuration_id', 'configuration_key',
                    'configuration_value', 'deleted',
                    'deleted_at']

    def __hash__(self):
        return self.configuration_key.__hash__()


class DBDatastoreConfigurationParameters(dbmodels.DatabaseModelBase):
    """Model for storing the configuration parameters on a datastore."""
    _auto_generated_attrs = ['id']
    _data_fields = [
        'name',
        'datastore_version_id',
        'restart_required',
        'max_size',
        'min_size',
        'data_type',
        'deleted',
        'deleted_at',
    ]
    _table_name = "datastore_configuration_parameters"
    preserve_on_delete = True


class DatastoreConfigurationParameters(object):
    def __init__(self, db_info):
        self.db_info = db_info

    @staticmethod
    def create(**kwargs):
        """Create a configuration parameter for a datastore version."""

        # Do we already have a parameter in the db?
        # yes: and its deleted then modify the param
        # yes: and its not deleted then error on create.
        # no: then just create the new param
        ds_v_id = kwargs.get('datastore_version_id')
        config_param_name = kwargs.get('name')
        try:
            param = DatastoreConfigurationParameters.load_parameter_by_name(
                ds_v_id,
                config_param_name,
                show_deleted=True)
            if param.deleted == 1:
                param.restart_required = kwargs.get('restart_required')
                param.data_type = kwargs.get('data_type')
                param.max_size = kwargs.get('max_size')
                param.min_size = kwargs.get('min_size')
                param.deleted = 0
                param.save()
                return param
            else:
                raise exception.ConfigurationParameterAlreadyExists(
                    parameter_name=config_param_name,
                    datastore_version=ds_v_id)
        except exception.NotFound:
            pass
        config_param = DBDatastoreConfigurationParameters.create(
            **kwargs)
        return config_param

    @staticmethod
    def delete(version_id, config_param_name):
        config_param = DatastoreConfigurationParameters.load_parameter_by_name(
            version_id, config_param_name)
        config_param.deleted = True
        config_param.deleted_at = datetime.utcnow()
        config_param.save()

    @classmethod
    def load_parameters(cls, datastore_version_id, show_deleted=False):
        try:
            if show_deleted:
                return DBDatastoreConfigurationParameters.find_all(
                    datastore_version_id=datastore_version_id
                )
            else:
                return DBDatastoreConfigurationParameters.find_all(
                    datastore_version_id=datastore_version_id,
                    deleted=False
                )
        except exception.NotFound:
            raise exception.NotFound(uuid=datastore_version_id)

    @classmethod
    def load_parameter(cls, config_id, show_deleted=False):
        try:
            if show_deleted:
                return DBDatastoreConfigurationParameters.find_by(
                    id=config_id
                )
            else:
                return DBDatastoreConfigurationParameters.find_by(
                    id=config_id, deleted=False
                )
        except exception.NotFound:
            raise exception.NotFound(uuid=config_id)

    @classmethod
    def load_parameter_by_name(cls, datastore_version_id, config_param_name,
                               show_deleted=False):
        try:
            if show_deleted:
                return DBDatastoreConfigurationParameters.find_by(
                    datastore_version_id=datastore_version_id,
                    name=config_param_name
                )
            else:
                return DBDatastoreConfigurationParameters.find_by(
                    datastore_version_id=datastore_version_id,
                    name=config_param_name,
                    deleted=False
                )
        except exception.NotFound:
            raise exception.NotFound(uuid=config_param_name)


def create_or_update_datastore_configuration_parameter(name,
                                                       datastore_version_id,
                                                       restart_required,
                                                       data_type,
                                                       max_size,
                                                       min_size):
    get_db_api().configure_db(CONF)
    datastore_version = dstore_models.DatastoreVersion.load_by_uuid(
        datastore_version_id)
    try:
        config = DatastoreConfigurationParameters.load_parameter_by_name(
            datastore_version_id, name, show_deleted=True)
        config.restart_required = restart_required
        config.max_size = max_size
        config.min_size = min_size
        config.data_type = data_type
        get_db_api().save(config)
    except exception.NotFound:
        config = DBDatastoreConfigurationParameters(
            id=utils.generate_uuid(),
            name=name,
            datastore_version_id=datastore_version.id,
            restart_required=restart_required,
            data_type=data_type,
            max_size=max_size,
            min_size=min_size,
            deleted=False,
        )
        get_db_api().save(config)


def load_datastore_configuration_parameters(datastore,
                                            datastore_version,
                                            config_file):
    get_db_api().configure_db(CONF)
    (ds, ds_v) = dstore_models.get_datastore_version(
        type=datastore, version=datastore_version, return_inactive=True)
    with open(config_file) as f:
        config = json.load(f)
        for param in config['configuration-parameters']:
            create_or_update_datastore_configuration_parameter(
                param['name'],
                ds_v.id,
                param['restart_required'],
                param['type'],
                param.get('max'),
                param.get('min'),
            )


def persisted_models():
    return {
        'configurations': DBConfiguration,
        'configuration_parameters': DBConfigurationParameter,
        'datastore_configuration_parameters': DBDatastoreConfigurationParameters,  # noqa
    }
