from parallels.core import get_logger
from parallels.core.connections.database_servers.base import DatabaseServer
from parallels.core.converter.database import TargetDatabaseConflictingObject
from parallels.core.utils.common import format_multiline_list
from parallels.core.utils.database_server_type import DatabaseServerType
from parallels.core.utils.name_conflicts import ConflictingHostingObjectDescription, SourceConflictingObject
from parallels.core.utils.plesk_utils import get_databases_of_server
from parallels.plesk.source.web import messages

logger = get_logger(__name__)


class DatabaseConflictingObjectDescription(ConflictingHostingObjectDescription):
    def __init__(self, global_context):
        """
        :type global_context: parallels.plesk.source.web.global_context.WebGlobalMigrationContext
        """
        self._global_context = global_context

    def get_type_id(self):
        """Get string identifier of object type, used not to mix renames in persistent storage.

        :rtype: str | unicode
        """
        return 'database'

    def list_target_objects(self):
        """Get list of objects on target panel, which may conflict with migrated objects from source panels

        :rtype: list[parallels.core.utils.name_conflicts.TargetConflictingObject]
        """
        target_databases = set()

        for database in self._global_context.list_all_migrated_databases():
            target_db_server = database.target_server

            if target_db_server is None:
                continue

            assert isinstance(target_db_server, DatabaseServer)

            databases = get_databases_of_server(
                self._global_context.conn.target.plesk_server, DatabaseServerType.MYSQL, target_db_server.host()
            )

            for db in databases:
                target_databases.add((
                    db['subscription_name'], db['database_name'], DatabaseServerType.MYSQL,
                    target_db_server.host(), target_db_server.port()
                ))

            panel_database_names = {db['database_name'] for db in databases}
            logger.fdebug(
                messages.PANEL_DATABASES,
                db_server=target_db_server.description(), databases=format_multiline_list(sorted(panel_database_names))
            )
            real_database_names = set(target_db_server.list_databases())
            if self._global_context.conn.target.plesk_server.is_windows():
                # case-insensitive comparison for database names on Windows
                other_database_names = (
                    {name.lower() for name in real_database_names} -
                    {name.lower() for name in panel_database_names}
                )
            else:
                other_database_names = real_database_names - panel_database_names

            logger.fdebug(
                messages.OTHER_DATABASES,
                db_server=target_db_server.description(), databases=format_multiline_list(sorted(other_database_names))
            )
            for db_name in other_database_names:
                target_databases.add((
                    None, db_name, DatabaseServerType.MYSQL,
                    target_db_server.host(), target_db_server.port()
                ))

        return [
            TargetDatabaseConflictingObject(
                subscription_name, database_name, database_type, database_host, database_port
            )
            for subscription_name, database_name, database_type, database_host, database_port
            in target_databases
        ]

    def list_source_objects(self):
        """Get list of objects in source panels, for which we should resolve name conflicts

        :rtype: list[parallels.plesk.source.web.conflicts.database.SourceDatabaseConflictingObject]
        """
        source_databases = []

        for database in self._global_context.list_all_migrated_databases():
            source_databases.append(SourceDatabaseConflictingObject(database))

        return source_databases


class SourceDatabaseConflictingObject(SourceConflictingObject):
    def __init__(self, database):
        """
        :type database: parallels.plesk.source.web.migrated_database.WebMigratedDatabase
        """
        self._database = database

    def get_name(self):
        """Get name of the object - that one that should be changed in case of conflicts.

        :rtype: str | unicode
        """
        return self._database.target_name

    def get_key(self):
        """Get key of the object - what differs 2 objects with the same name.

        Objects with the same key and the same name conflict with each other.
        Objects with different keys never conflict.

        Returns tuple (database type, target database server host, target database server port)

        :rtype: tuple | None
        """
        target_db_server = self._database.target_server

        if target_db_server is None:
            # if target database server is unknown - resolve conflicts with any database name
            return DatabaseServerType.MYSQL, None, None

        assert isinstance(target_db_server, DatabaseServer)

        return DatabaseServerType.MYSQL, target_db_server.host(), target_db_server.port()

    def get_subscription_name(self):
        """Get name of subscription which owns the object.

        :rtype: str | unicode
        """
        return self._database.target_subscription_name

    def set_name(self, new_name):
        """Rename object in case of conflicts

        :type new_name: str | unicode
        """
        self._database.set_target_name(new_name)
