#  Copyright 2013 Mirantis Inc.
#  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.

import yaml
from trove.common import cfg
from trove.common import utils
from trove.common import exception
from trove.common import instance as rd_instance
from trove.guestagent.common import operating_system
from trove.guestagent.datastore.cassandra import system
from trove.guestagent.datastore import service
from trove.guestagent import pkg
from trove.openstack.common import log as logging
from trove.openstack.common.gettextutils import _


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

packager = pkg.Package()


class CassandraApp(object):
    """Prepares DBaaS on a Guest container."""

    def __init__(self, status):
        """By default login with root no password for initial setup. """
        self.state_change_wait_time = CONF.state_change_wait_time
        self.status = status

    def install_if_needed(self, packages):
        """Prepare the guest machine with a cassandra server installation"""
        LOG.info(_("Preparing Guest as Cassandra Server"))
        if not packager.pkg_is_installed(packages):
            self._install_db(packages)
        LOG.info(_("Dbaas install_if_needed complete"))

    def complete_install_or_restart(self):
        self.status.end_install_or_restart()

    def _enable_db_on_boot(self):
        utils.execute_with_timeout(system.ENABLE_CASSANDRA_ON_BOOT,
                                   shell=True)

    def _disable_db_on_boot(self):
        utils.execute_with_timeout(system.DISABLE_CASSANDRA_ON_BOOT,
                                   shell=True)

    def init_storage_structure(self, mount_point):
        try:
            cmd = system.INIT_FS % mount_point
            utils.execute_with_timeout(cmd, shell=True)
        except exception.ProcessExecutionError as e:
            LOG.error(_("Error while initiating storage structure."))
            LOG.error(e)

    def start_db(self, update_db=False):
        self._enable_db_on_boot()
        try:
            utils.execute_with_timeout(system.START_CASSANDRA,
                                       shell=True)
        except exception.ProcessExecutionError:
            pass

        if not (self.status.
                wait_for_real_status_to_change_to(
                rd_instance.ServiceStatuses.RUNNING,
                self.state_change_wait_time,
                update_db)):
            try:
                utils.execute_with_timeout(system.CASSANDRA_KILL,
                                           shell=True)
            except exception.ProcessExecutionError as p:
                LOG.error(_("Error killing stalled Cassandra start command."))
                LOG.error(p)
            self.status.end_install_or_restart()
            raise RuntimeError(_("Could not start Cassandra"))

    def stop_db(self, update_db=False, do_not_start_on_reboot=False):
        if do_not_start_on_reboot:
            self._disable_db_on_boot()
        utils.execute_with_timeout(system.STOP_CASSANDRA,
                                   shell=True)

        if not (self.status.wait_for_real_status_to_change_to(
                rd_instance.ServiceStatuses.SHUTDOWN,
                self.state_change_wait_time, update_db)):
            LOG.error(_("Could not stop Cassandra"))
            self.status.end_install_or_restart()
            raise RuntimeError(_("Could not stop Cassandra"))

    def restart(self):
        try:
            self.status.begin_restart()
            LOG.info(_("Restarting DB"))
            self.stop_db()
            self.start_db()
        finally:
            self.status.end_install_or_restart()

    def _install_db(self, packages):
        """Install cassandra server"""
        LOG.debug(_("Installing cassandra server"))
        packager.pkg_install(packages, None, system.TIME_OUT)
        LOG.debug(_("Finished installing cassandra server"))

    def write_config(self, config_contents):
        LOG.info(_('Defining temp config holder at '
                 '%s') % system.CASSANDRA_TEMP_CONF)
        with open(system.CASSANDRA_TEMP_CONF, 'w+') as conf:
            conf.write(config_contents)
        LOG.info(_('Writing new config'))
        utils.execute_with_timeout("sudo", "mv",
                                   system.CASSANDRA_TEMP_CONF,
                                   system.CASSANDRA_CONF)
        LOG.info(_('Overriding old config'))

    def read_conf(self):
        """Returns cassandra.yaml in dict structure"""

        LOG.info(_("Opening cassandra.yaml"))
        with open(system.CASSANDRA_CONF, 'r') as config:
            LOG.info(_("Preparing YAML object from cassandra.yaml"))
            yamled = yaml.load(config.read())
        return yamled

    def update_config_with_single(self, key, value):
        """Updates single key:value in cassandra.yaml"""

        yamled = self.read_conf()
        yamled.update({key: value})
        LOG.info(_("Updating cassandra.yaml with %(key)s: %(value)s")
                 % {'key': key, 'value': value})
        dump = yaml.dump(yamled, default_flow_style=False)
        LOG.info(_("Dumping YAML to stream"))
        self.write_config(dump)

    def update_conf_with_group(self, group):
        """Updates group of key:value in cassandra.yaml"""

        yamled = self.read_conf()
        for key, value in group.iteritems():
            if key == 'seed':
                (yamled.get('seed_provider')[0].
                 get('parameters')[0].
                 update({'seeds': value}))
            else:
                yamled.update({key: value})
            LOG.info(_("Updating cassandra.yaml with %(key)s: %(value)s")
                     % {'key': key, 'value': value})
        dump = yaml.dump(yamled, default_flow_style=False)
        LOG.info(_("Dumping YAML to stream"))
        self.write_config(dump)

    def make_host_reachable(self):
        updates = {
            'rpc_address': "0.0.0.0",
            'listen_address': operating_system.get_ip_address(),
            'seed': operating_system.get_ip_address()
        }
        self.update_conf_with_group(updates)

    def start_db_with_conf_changes(self, config_contents):
        LOG.info(_("Starting cassandra with conf changes..."))
        LOG.info(_("inside the guest - cassandra is running %s...")
                 % self.status.is_running)
        if self.status.is_running:
            LOG.error(_("Cannot execute start_db_with_conf_changes because "
                        "cassandra state == %s!") % self.status)
            raise RuntimeError("Cassandra not stopped.")
        LOG.info(_("Initiating config."))
        self.write_config(config_contents)
        self.start_db(True)

    def reset_configuration(self, configuration):
        config_contents = configuration['config_contents']
        LOG.info(_("Resetting configuration"))
        self.write_config(config_contents)


class CassandraAppStatus(service.BaseDbStatus):

    def _get_actual_db_status(self):
        try:
            # If status check would be successful,
            # bot stdin and stdout would contain nothing
            out, err = utils.execute_with_timeout(system.CASSANDRA_STATUS,
                                                  shell=True)
            if "Connection error. Could not connect to" not in err:
                return rd_instance.ServiceStatuses.RUNNING
            else:
                return rd_instance.ServiceStatuses.SHUTDOWN
        except exception.ProcessExecutionError as e:
            LOG.error(_("Process execution %s") % e)
            return rd_instance.ServiceStatuses.SHUTDOWN
        except OSError as e:
            LOG.error(_("OS Error %s") % e)
            return rd_instance.ServiceStatuses.SHUTDOWN
