from collections import defaultdict

from parallels.core.logging import get_logger
from parallels.core import messages
from parallels.core.actions.base.common_action import CommonAction
from parallels.core.actions.utils.logging_properties import LoggingProperties
from parallels.core.reports.model.issue import Issue
from parallels.core.utils.common import format_list, is_run_on_windows
from parallels.core.utils.database_utils import is_mysql_secure_auth_enabled

logger = get_logger(__name__)


class CheckOldMySQLPasswords(CommonAction):
    """Pre-migration check for old MySQL password hashes, unusable in specific target MySQL configurations

    Briefly:
    Recent MySQL servers do not support old 16-byte password hashes if secure_auth configuration setting
    is enabled. Old 16-byte password hashes are used in MySQL < 4.1, and on other MySQL versions if old_passwords
    variable is enabled, so if we migrate users from such server and don't know plain text password, they
    won't be able to login to the target MySQL server.

    Refer to https://dev.mysql.com/doc/refman/5.5/en/password-hashing.html for more information.
    """

    def get_description(self):
        """Get short description of action as string

        :rtype: str
        """
        return messages.ACTION_CHECK_OLD_MYSQL_PASSWORDS_DESCRIPTION

    def get_failure_message(self, global_context):
        """
        :type global_context: parallels.core.global_context.GlobalMigrationContext
        """
        return messages.ACTION_CHECK_OLD_MYSQL_PASSWORDS_FAILURE

    def is_critical(self):
        """If action is critical or not

        If action is critical and it failed for a subscription, migration tool
        won't run the next operations for the subscription.

        :rtype: bool
        """
        return False

    def get_logging_properties(self):
        """Get how action should be logged to migration tools end-user

        :rtype: parallels.core.actions.utils.logging_properties.LoggingProperties
        """
        return LoggingProperties(compound=False)

    def run(self, global_context):
        """
        :type global_context: parallels.core.global_context.GlobalMigrationContext
        """
        servers = defaultdict(lambda: defaultdict(list))

        for subscription in global_context.iter_all_subscriptions():
            for user in subscription.raw_dump.iter_all_database_users():
                if user.dbtype != 'mysql':
                    continue
                if user.password.password_type != 'encrypted' or len(user.password.password_text) != 16:
                    continue

                target_mysql_server = subscription.db_target_servers.get('mysql')
                if target_mysql_server is not None:
                    servers[target_mysql_server][subscription.name].append(user.name)

        servers_with_secure_auth = {
            server: users_info for server, users_info in servers.items()
            if is_mysql_secure_auth_enabled(server)
        }

        if len(servers_with_secure_auth) == 0:
            return

        servers_and_users = "\n".join(
            "%s:\n%s" % (
                server.description(), "\n".join(
                    messages.MYSQL_SUBSCRIPTION_USERS.format(
                        subscription=subscription, users=format_list(users)
                    )
                    for subscription, users in users_info.items()
                )
            ) for server, users_info in servers_with_secure_auth.items()
        )
        global_context.pre_check_report.add_issue(
            'mysql_insecure_passwords', Issue.SEVERITY_WARNING,
            messages.MYSQL_INSECURE_PASSWORDS_PROBLEM.format(
                servers_and_users=servers_and_users
            ) + "\n",
            (
                messages.MYSQL_INSECURE_PASSWORDS_SOLUTION_WINDOWS if is_run_on_windows()
                else messages.MYSQL_INSECURE_PASSWORDS_SOLUTION_UNIX
            )
        )
