from parallels.core.connections.database_servers.base import DatabaseServer
from parallels.core.utils.name_conflicts import (
    ConflictingHostingObjectDescription, SourceConflictingObject, TargetConflictingObject
)
from parallels.core.utils.plesk_utils import get_databases_of_server


class DatabaseConflictingObjectDescription(ConflictingHostingObjectDescription):
    def __init__(self, global_context):
        """
        :type global_context: parallels.core.global_context.GlobalMigrationContext
        """
        self._global_context = global_context

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

        For example, for system user it could return 'sysuser', for customer it could return 'customer',
        for reseller it could return 'reseller' and so on.

        :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 subscription in self._global_context.iter_all_subscriptions():
            for database in subscription.raw_dump.iter_all_databases():
                target_db_server = subscription.db_target_servers.get(database.dbtype)

                if target_db_server is None:
                    continue

                assert isinstance(target_db_server, DatabaseServer)

                databases = get_databases_of_server(
                    subscription.panel_target_server, database.dbtype, target_db_server.host()
                )

                for db in databases:
                    target_databases.add((
                        db['subscription_name'], db['database_name'], database.dbtype,
                        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.core.utils.name_conflicts.SourceConflictingObject]
        """
        source_databases = []

        for subscription in self._global_context.iter_all_subscriptions():
            for database in subscription.raw_dump.iter_all_databases():
                source_databases.append(
                    SourceDatabaseConflictingObject(subscription, database)
                )

        return source_databases


class SourceDatabaseConflictingObject(SourceConflictingObject):
    def __init__(self, subscription, database):
        """
        :type subscription: parallels.core.migrated_subscription.MigratedSubscription
        :type database: parallels.core.dump.data_model.Database
        """
        self._subscription = subscription
        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.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._subscription.db_target_servers.get(self._database.dbtype)

        if target_db_server is None:
            # if target database server is unknown - resolve conflicts with any database name
            return self._database.dbtype, None, None

        assert isinstance(target_db_server, DatabaseServer)

        return self._database.dbtype, 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._subscription.name

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

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


class TargetDatabaseConflictingObject(TargetConflictingObject):
    """
    :type _subscription_name: str | unicode | None
    :type _database_name:  str | unicode
    :type _database_type: str | unicode
    :type _database_host: str | unicode
    :type _database_port: str | unicode | int | None
    """

    def __init__(self, subscription_name, database_name, database_type, database_host, database_port):
        """
        :type subscription_name: str | unicode | None
        :type database_name:  str | unicode
        :type database_type: str | unicode
        :type database_host: str | unicode
        :type database_port: str | unicode | int | None
        """
        self._subscription_name = subscription_name
        self._database_name = database_name
        self._database_type = database_type
        self._database_host = database_host
        self._database_port = database_port

    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_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.

        :rtype: tuple | None
        """
        return self._database_type, self._database_host, self._database_port

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

        :rtype: str | unicode | None
        """
        return self._subscription_name
