# Copyright (c) 2015, MapR Technologies
#
# 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 collections as c

from oslo_log import log as logging
import six

import sahara.plugins.mapr.domain.configuration_file as cf
import sahara.plugins.mapr.domain.service as s
import sahara.plugins.mapr.services.hive.hive as hive
from sahara.plugins.mapr.services.spark import spark
import sahara.plugins.mapr.util.general as g
import sahara.utils.files as f

LOG = logging.getLogger(__name__)

db_spec = c.namedtuple('DatabaseSpec', ['db_name', 'user', 'password'])


@six.add_metaclass(s.Single)
class MySQL(s.Service):
    METRICS_SPECS = db_spec('metrics', 'maprmetrics', 'mapr')
    HUE_SPECS = db_spec('hue', 'maprhue', 'mapr')
    METASTORE_SPECS = db_spec('metastore', 'maprmetastore', 'mapr')
    RDBMS_SPECS = db_spec('rdbms', 'maprrdbms', 'mapr')
    OOZIE_SPECS = db_spec('oozie', 'maproozie', 'mapr')

    SELECT_DATA = 'mysql -uroot --skip-column-names -e "%s"| grep -E "\w+"'
    GET_DBS_LIST = SELECT_DATA % 'SHOW DATABASES'
    GET_USERS_HOSTS = (
        SELECT_DATA % "SELECT Host FROM mysql.user WHERE mysql.user.User='%s'"
    )

    SCHEMA_PATH = (
        '/opt/mapr/hive/hive-{0}/scripts/metastore/upgrade/mysql/'
        'hive-schema-{0}.0.mysql.sql')
    DRIVER_CLASS = 'com.mysql.jdbc.Driver'
    MYSQL_SERVER_PORT = 3306
    MYSQL_INSTALL_SCRIPT = 'plugins/mapr/resources/install_mysql.sh'
    INSTALL_PACKAGES_TIMEOUT = 1800

    def __init__(self):
        super(MySQL, self).__init__()
        self._ui_name = 'MySQL'

    @staticmethod
    def _get_db_daemon_name(distro):
        if distro.lower() == 'ubuntu':
            return 'mysql'
        if distro.lower() in ['centos', 'redhatenterpriseserver', 'suse']:
            return 'mysqld'
        return None

    @staticmethod
    def _execute_script(instance, script_path, script_text=None,
                        user='root', password=None):
        with instance.remote() as r:
            if script_text:
                r.write_file_to(script_path, script_text, run_as_root=True)
            LOG.debug('Executing SQL script {path}'.format(path=script_path))
            r.execute_command(("mysql %s %s < %s" %
                               ('-u' + user if user else '',
                                '-p' + password if password else '',
                                script_path)),
                              run_as_root=True)

    @staticmethod
    def _create_service_db(instance, specs):
        f_name = 'create_db_%s.sql' % specs.db_name
        script = MySQL._create_script_obj(f_name, 'create_database.sql',
                                          db_name=specs.db_name,
                                          user=specs.user,
                                          password=specs.password)
        MySQL._execute_script(instance, script.remote_path, script.render())

    @staticmethod
    def _create_metrics_db(instance, databases, instances):
        if MySQL.METRICS_SPECS.db_name not in databases:
            MySQL._create_service_db(instance, MySQL.METRICS_SPECS)
            MySQL._execute_script(instance=instance,
                                  script_path='/opt/mapr/bin/setup.sql')
        MySQL._grant_access(instance, MySQL.METRICS_SPECS, instances)

    @staticmethod
    def _create_hue_db(instance, databases, instances):
        if MySQL.HUE_SPECS.db_name not in databases:
            MySQL._create_service_db(instance, MySQL.HUE_SPECS)
        MySQL._grant_access(instance, MySQL.HUE_SPECS, instances)

    @staticmethod
    def _create_rdbms_db(instance, databases, instances):
        if MySQL.RDBMS_SPECS.db_name not in databases:
            MySQL._create_service_db(instance, MySQL.RDBMS_SPECS)
        MySQL._grant_access(instance, MySQL.RDBMS_SPECS, instances)

    @staticmethod
    def _create_metastore_db(instance, cluster_context, databases, instances):
        hive_meta = cluster_context.get_instance(hive.HIVE_METASTORE)

        if not hive_meta:
            return

        db_name = MySQL.METASTORE_SPECS.db_name
        if db_name not in databases:
            MySQL._create_service_db(instance, MySQL.METASTORE_SPECS)
            MySQL._grant_access(instance, MySQL.METASTORE_SPECS, instances)
            with hive_meta.remote() as r:
                hive_serv = cluster_context.get_service(hive.HIVE_METASTORE)
                schema_path = MySQL.SCHEMA_PATH.format(hive_serv.version)
                script = MySQL._create_script_obj('hive_schema.sql',
                                                  'hive_schema.sql',
                                                  db_name=db_name,
                                                  path=schema_path)
                r.write_file_to(script.remote_path, script.render())
                args = {
                    'user': MySQL.METASTORE_SPECS.user,
                    'password': MySQL.METASTORE_SPECS.password,
                    'host': instance.internal_ip,
                    'path': script.remote_path
                }
                cmd = 'mysql -h{host} -u{user} -p{password} < {path}'
                r.execute_command(cmd.format(**args), run_as_root=True)
        else:
            MySQL._grant_access(instance, MySQL.METASTORE_SPECS, instances)

    @staticmethod
    def _create_oozie_db(instance, databases, instances):
        if MySQL.OOZIE_SPECS.db_name not in databases:
            MySQL._create_service_db(instance, MySQL.OOZIE_SPECS)
        MySQL._grant_access(instance, MySQL.OOZIE_SPECS, instances)

    @staticmethod
    def start_mysql_server(cluster_context):
        LOG.debug('Starting MySQL Server')
        instance = MySQL.get_db_instance(cluster_context)
        distro = cluster_context.distro
        with instance.remote() as r:
            r.execute_command(('service %s restart' %
                               MySQL._get_db_daemon_name(distro.name)),
                              run_as_root=True)
        LOG.debug('MySQL Server successfully started')

    @staticmethod
    def get_databases_list(db_instance):
        with db_instance.remote() as r:
            ec, out = r.execute_command(MySQL.GET_DBS_LIST)
            if out:
                return out.splitlines()
        return list()

    @staticmethod
    def get_user_hosts(db_instance, username):
        with db_instance.remote() as r:
            ec, out = r.execute_command(MySQL.GET_USERS_HOSTS % username)
            if out:
                return out.splitlines()
        return list()

    @staticmethod
    def get_db_instance(context):
        return context.oozie_server or context.get_instance(spark.SPARK_MASTER)

    @staticmethod
    def create_databases(cluster_context, instances):
        db_instance = MySQL.get_db_instance(cluster_context)
        databases = MySQL.get_databases_list(db_instance)
        MySQL._create_metrics_db(db_instance, databases, instances)
        MySQL._create_hue_db(db_instance, databases, instances)
        MySQL._create_rdbms_db(db_instance, databases, instances)
        MySQL._create_oozie_db(db_instance, databases, instances)
        MySQL._create_metastore_db(
            db_instance, cluster_context, databases, instances)

    @staticmethod
    def _create_script_obj(filename, template, **kwargs):
        script = cf.TemplateFile(filename)
        script.remote_path = '/tmp/'
        script.parse(f.get_file_text(
            'plugins/mapr/services/mysql/resources/%s' % template))
        for k, v in six.iteritems(kwargs):
            script.add_property(k, v)
        return script

    @staticmethod
    def _grant_access(instance, specs, instances):
        f_name = 'grant_access_%s.sql' % specs.db_name
        ips = [i.internal_ip for i in instances]
        user_hosts = MySQL.get_user_hosts(instance, specs.user)
        script = MySQL._create_script_obj(f_name, 'grant_access.sql',
                                          hosts=set(ips)-set(user_hosts),
                                          db_name=specs.db_name,
                                          user=specs.user,
                                          password=specs.password)
        MySQL._execute_script(instance, script.remote_path, script.render())

    @staticmethod
    def install_mysql(instance, distro_name):
        g.run_script(instance, MySQL.MYSQL_INSTALL_SCRIPT, 'root', distro_name)
