# Copyright 2010-2012 OpenStack Foundation
# 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 re
import time

import eventlet
from oslo_log import log as logging

from trove.common import exception as rd_exception
from trove.instance import service_status as srvstatus
from trove.tests.util import unquote_user_host

DB = {}
LOG = logging.getLogger(__name__)
BACKUP_SIZE = 0.14


class FakeGuest(object):

    def __init__(self, id):
        self.id = id
        self.users = {}
        self.dbs = {}
        self.root_was_enabled = False
        self.version = 1
        self.grants = {}
        self.overrides = {}

        # Our default admin user.
        self._create_user({
            "_name": "os_admin",
            "_host": "%",
            "_password": "12345",
            "_databases": [],
        })

    def get_hwinfo(self):
        return {'mem_total': 524288, 'num_cpus': 1}

    def get_diagnostics(self):
        return {
            'version': str(self.version),
            'fd_size': 64,
            'vm_size': 29096,
            'vm_peak': 29160,
            'vm_rss': 2872,
            'vm_hwm': 2872,
            'threads': 2
        }

    def update_guest(self):
        LOG.debug("Updating guest %s", self.id)
        self.version += 1

    def _check_username(self, username):
        unsupported_chars = re.compile(r"""^\s|\s$|'|"|;|`|,|/|\\""")
        if (not username or
                unsupported_chars.search(username) or
                ("%r" % username).find("\\") != -1):
            raise ValueError("'%s' is not a valid user name." % username)
        if len(username) > 16:
            raise ValueError("User name '%s' is too long. Max length = 16" %
                             username)

    def change_passwords(self, users):
        for user in users:
            # Use the model to check validity.
            username = user['name']
            self._check_username(username)
            hostname = user['host']
            password = user['password']
            if (username, hostname) not in self.users:
                raise rd_exception.UserNotFound(
                    "User %s@%s cannot be found on the instance."
                    % (username, hostname))
            self.users[(username, hostname)]['password'] = password

    def update_attributes(self, username, hostname, user_attrs):
        LOG.debug("Updating attributes")
        self._check_username(username)
        if (username, hostname) not in self.users:
            raise rd_exception.UserNotFound(
                "User %s@%s cannot be found on the instance."
                % (username, hostname))
        new_name = user_attrs.get('name')
        new_host = user_attrs.get('host')
        new_password = user_attrs.get('password')
        old_name = username
        old_host = hostname
        name = new_name or old_name
        host = new_host or old_host
        if new_name or new_host:
            old_grants = self.grants.get((old_name, old_host), set())
            self._create_user({
                "_name": name,
                "_host": host,
                "_password": self.users[(old_name, host)]['_password'],
                "_databases": [],
            })
            self.grants[(name, host)] = old_grants
            del self.users[(old_name, old_host)]
        if new_password:
            self.users[(name, host)]['_password'] = new_password

    def create_database(self, databases):
        for db in databases:
            self.dbs[db['_name']] = db

    def create_user(self, users):
        for user in users:
            self._create_user(user)

    def _create_user(self, user):
        username = user['_name']
        self._check_username(username)
        hostname = user['_host']
        if hostname is None:
            hostname = '%'
        self.users[(username, hostname)] = user
        print("CREATING %s @ %s" % (username, hostname))
        databases = [db['_name'] for db in user['_databases']]
        self.grant_access(username, hostname, databases)
        return user

    def delete_database(self, database):
        if database['_name'] in self.dbs:
            del self.dbs[database['_name']]

    def enable_root(self):
        self.root_was_enabled = True
        return self._create_user({
            "_name": "root",
            "_host": "%",
            "_password": "12345",
            "_databases": [],
        })

    def enable_root_with_password(self, root_password=None):
        self.root_was_enabled = True
        return self._create_user({
            "_name": "root",
            "_host": "%",
            "_password": "12345",
            "_databases": [],
        })

    def disable_root(self):
        self.delete_user({
            "_name": "root",
            "_host": "%"})

    def delete_user(self, user):
        username = user['_name']
        self._check_username(username)
        hostname = user['_host']
        self.grants[(username, hostname)] = set()
        if (username, hostname) in self.users:
            del self.users[(username, hostname)]

    def is_root_enabled(self):
        return self.root_was_enabled

    def _list_resource(self, resource, limit=None, marker=None,
                       include_marker=False):
        names = sorted([name for name in resource])
        if marker in names:
            if not include_marker:
                # Cut off everything left of and including the marker item.
                names = names[names.index(marker) + 1:]
            else:
                names = names[names.index(marker):]
        next_marker = None
        if limit:
            if len(names) > limit:
                next_marker = names[limit - 1]
            names = names[:limit]
        return [resource[name] for name in names], next_marker

    def list_databases(self, limit=None, marker=None, include_marker=False):
        return self._list_resource(self.dbs, limit, marker, include_marker)

    def list_users(self, limit=None, marker=None, include_marker=False):
        # The markers for users are a composite of the username and hostname.
        names = sorted(["%s@%s" % (name, host) for (name, host) in self.users])
        if marker in names:
            if not include_marker:
                # Cut off everything left of and including the marker item.
                names = names[names.index(marker) + 1:]
            else:
                names = names[names.index(marker):]
        next_marker = None
        if limit:
            if len(names) > limit:
                next_marker = names[limit - 1]
            names = names[:limit]
        return ([self.users[unquote_user_host(userhost)]
                 for userhost in names], next_marker)

    def get_user(self, username, hostname):
        self._check_username(username)
        for (u, h) in self.users:
            print("%r @ %r" % (u, h))
        if (username, hostname) not in self.users:
            raise rd_exception.UserNotFound(
                "User %s@%s cannot be found on the instance."
                % (username, hostname))
        return self.users.get((username, hostname), None)

    def prepare(self, memory_mb, packages, databases, users, device_path=None,
                mount_point=None, backup_info=None, config_contents=None,
                root_password=None, overrides=None, cluster_config=None,
                snapshot=None, modules=None):
        from trove.guestagent.models import AgentHeartBeat
        from trove.instance.models import DBInstance
        from trove.instance.models import InstanceServiceStatus
        LOG.debug("users... %s", users)
        LOG.debug("databases... %s", databases)
        instance_name = DBInstance.find_by(id=self.id).name
        self.create_user(users)
        self.create_database(databases)
        self.overrides = overrides or {}

        def update_db():
            status = InstanceServiceStatus.find_by(instance_id=self.id)
            if instance_name.endswith('GUEST_ERROR'):
                status.status = srvstatus.ServiceStatuses.FAILED
            else:
                status.status = srvstatus.ServiceStatuses.HEALTHY
            status.save()
            AgentHeartBeat.create(instance_id=self.id)
        eventlet.spawn_after(3.5, update_db)

    def _set_task_status(self, new_status='HEALTHY'):
        from trove.instance.models import InstanceServiceStatus
        print("Setting status to %s" % new_status)
        states = {'HEALTHY': srvstatus.ServiceStatuses.HEALTHY,
                  'SHUTDOWN': srvstatus.ServiceStatuses.SHUTDOWN,
                  }
        status = InstanceServiceStatus.find_by(instance_id=self.id)
        status.status = states[new_status]
        status.save()

    def restart(self):
        # All this does is restart, and shut off the status updates while it
        # does so. So there's actually nothing to do to fake this out except
        # take a nap.
        print("Sleeping for a second.")
        time.sleep(1)
        self._set_task_status('HEALTHY')

    def reset_configuration(self, config):
        # There's nothing to do here, since there is no config to update.
        pass

    def start_db_with_conf_changes(self, config_contents, ds_version):
        time.sleep(2)
        self._set_task_status('HEALTHY')

    def stop_db(self):
        self._set_task_status('SHUTDOWN')

    def get_volume_info(self):
        """Return used and total volume filesystem information in GB."""
        return {'used': 0.16, 'total': 4.0}

    def grant_access(self, username, hostname, databases):
        """Add a database to a users's grant list."""
        if (username, hostname) not in self.users:
            raise rd_exception.UserNotFound(
                "User %s cannot be found on the instance." % username)
        current_grants = self.grants.get((username, hostname), set())
        for db in databases:
            current_grants.add(db)
        self.grants[(username, hostname)] = current_grants

    def revoke_access(self, username, hostname, database):
        """Remove a database from a users's grant list."""
        if (username, hostname) not in self.users:
            raise rd_exception.UserNotFound(
                "User %s cannot be found on the instance." % username)
        if database not in self.grants.get((username, hostname), set()):
            raise rd_exception.DatabaseNotFound(
                "Database %s cannot be found on the instance." % database)
        current_grants = self.grants.get((username, hostname), set())
        if database in current_grants:
            current_grants.remove(database)
        self.grants[(username, hostname)] = current_grants

    def list_access(self, username, hostname):
        if (username, hostname) not in self.users:
            raise rd_exception.UserNotFound(
                "User %s cannot be found on the instance." % username)
        current_grants = self.grants.get((username, hostname), set())
        dbs = [{'_name': db,
                '_collate': '',
                '_character_set': '',
                } for db in current_grants]
        return dbs

    def create_backup(self, backup_info):
        from trove.backup.models import Backup
        from trove.backup.state import BackupState
        backup = Backup.get_by_id(context=None,
                                  backup_id=backup_info['id'])

        def finish_create_backup():
            backup.state = BackupState.COMPLETED
            backup.location = 'http://localhost/path/to/backup'
            backup.checksum = 'fake-md5-sum'
            backup.size = BACKUP_SIZE
            backup.save()
        eventlet.spawn_after(10, finish_create_backup)

    def mount_volume(self, device_path=None, mount_point=None):
        pass

    def unmount_volume(self, device_path=None, mount_point=None):
        pass

    def resize_fs(self, device_path=None, mount_point=None, online=False):
        pass

    def update_overrides(self, overrides, remove=False):
        self.overrides = overrides

    def apply_overrides(self, overrides):
        self.overrides = overrides

    def get_replication_snapshot(self, snapshot_info,
                                 replica_source_config=None):
        self.create_backup(snapshot_info)
        return {
            'dataset':
            {
                'datastore_manager': 'mysql',
                'dataset_size': '0.0',
                'volume_size': '10.0',
                'snapshot_id': None
            },
            'replication_strategy': 'replication_strategy',
            'master': '1',
            'log_position': '100'
        }

    def attach_replication_slave(self, snapshot, slave_config):
        pass

    def backup_required_for_replication(self):
        return True

    def post_processing_required_for_replication(self):
        return False

    def module_list(self, context, include_contents=False):
        return []

    def module_apply(self, context, modules=None):
        return []

    def module_remove(self, context, module=None):
        pass


def get_or_create(id):
    if id not in DB:
        DB[id] = FakeGuest(id)
    return DB[id]


def fake_create_guest_client(context, id, manager=None):
    return get_or_create(id)
