from parallels.core import messages
from parallels.core.actions.base.subscription_action import SubscriptionAction
from parallels.core.actions.post_migration_checks.utils import should_run_post_migration_checks
from parallels.core.hosting_check.run_checks import run_entities
from parallels.core.logging import get_logger
from parallels.core.utils.common import group_by, is_empty
from parallels.core.utils.database_utils import get_mssql_host_for_target_server
from parallels.hosting_check import DomainMSSQLDatabases, DomainMySQLDatabases, DomainPostgreSQLDatabases, \
    TargetPanelDatabase, MySQLDatabase, MySQLDatabaseAccess, User, MSSQLDatabase, MSSQLDatabaseSourceAccess, \
    MSSQLDatabaseTargetAccess, PostgreSQLDatabase, PostgreSQLDatabaseAccess
from parallels.hosting_check.checkers.mysql_database_checker import UnixMySQLClientCLI, WindowsMySQLClientCLI

logger = get_logger(__name__)


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

        :rtype: str | unicode
        """
        return messages.ACTION_TEST_DATABASES_DESCRIPTION

    def get_failure_message(self, global_context, subscription):
        """
        :type global_context: parallels.core.global_context.GlobalMigrationContext
        :type subscription: parallels.core.migrated_subscription.MigratedSubscription
        :rtype: str | unicode
        """
        return messages.ACTION_TEST_DATABASES_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 filter_subscription(self, global_context, subscription):
        """Check if we should run this action on given subscription or not

        :type global_context: parallels.core.global_context.GlobalMigrationContext
        :type subscription: parallels.core.migrated_subscription.MigratedSubscription
        :rtype: bool
        """
        return should_run_post_migration_checks(global_context, 'databases')

    def run(self, global_context, subscription):
        """
        :type global_context: parallels.core.global_context.GlobalMigrationContext
        :type subscription: parallels.core.migrated_subscription.MigratedSubscription
        """
        subscription_report = subscription.get_report(global_context.post_migration_check_report_writer)
        database_service_report = subscription_report.subtarget(u'Database service', subscription.name)

        database_service_entities = self._get_database_check_entities(global_context, subscription)
        run_entities(global_context, database_service_entities, database_service_report)

    def _get_database_check_entities(self, global_context, subscription):
        """
        :type global_context: parallels.core.global_context.GlobalMigrationContext
        :type subscription: parallels.core.migrated_subscription.MigratedSubscription
        """
        result = []

        # initialize entities to put information about databases to check
        mysql_databases_entity = DomainMySQLDatabases(
            domain_name=subscription.name,
            target_panel_databases=[],
            databases=[]
        )
        result.append(mysql_databases_entity)
        mssql_databases_entity = DomainMSSQLDatabases(
            domain_name=subscription.name,
            target_panel_databases=[],
            databases=[]
        )
        result.append(mssql_databases_entity)
        pgsql_databases_entity = DomainPostgreSQLDatabases(
            domain_name=subscription.name,
            target_panel_databases=[],
            databases=[]
        )
        result.append(pgsql_databases_entity)

        database_info_source = DatabasesInfoSource(global_context)

        # fill information about databases that exist on target panel
        for entity, db_type in (
            (mysql_databases_entity, 'mysql'),
            (mssql_databases_entity, 'mssql'),
            (pgsql_databases_entity, 'postgresql')
        ):
            target_databases = global_context.hosting_repository.database.get_list_by_subscription(
                subscription_name=subscription.name, db_type=db_type
            )
            for database in target_databases:
                entity.target_panel_databases.append(
                    TargetPanelDatabase(
                        name=database.name,
                        users=database.user_names
                    )
                )

        # fill information about databases that are expected to be restored
        subscription_databases = database_info_source.list_databases_to_copy().get(
            subscription.name, []
        )
        for db_info in subscription_databases:
            db_type = db_info.source_database_server.type()
            if db_type == 'mysql':
                converted_db = self._get_mysql_database_object(database_info_source, subscription, db_info)
                if converted_db is not None:
                    mysql_databases_entity.databases.append(converted_db)
            elif db_type == 'mssql':
                mssql_databases_entity.databases.append(
                    self._get_mssql_database_object(database_info_source, subscription, db_info)
                )
            elif db_type == 'postgresql':
                pgsql_databases_entity.databases.append(
                    self._get_pgsql_database_object(database_info_source, subscription, db_info)
                )

        return result

    @staticmethod
    def _get_mysql_database_object(database_info_source, subscription, database_info):
        """Initialize and return MySQLDatabase object

        :type database_info: parallels.core.utils.database_utils.DatabaseInfo
        """

        source = database_info.source_database_server
        target = database_info.target_database_server

        if source.is_windows() == target.is_windows():

            source_mysql_client_cli = database_info_source.get_source_mysql_client_cli(database_info)

            if not target.is_windows():
                target_mysql_client_cli = UnixMySQLClientCLI(target)
            else:
                target_mysql_client_cli = WindowsMySQLClientCLI(
                    target, target.mysql_bin
                )

            users = database_info_source.get_db_users(
                subscription.name,
                database_info.source_database_name,
                source.type()
            )

            return MySQLDatabase(
                source_access=MySQLDatabaseAccess(
                    mysql_client_cli=source_mysql_client_cli,
                    host=source.host(),
                    port=source.port(),
                    admin_user=User(
                        source.user(),
                        source.password()
                    ),
                ),
                target_access=MySQLDatabaseAccess(
                    mysql_client_cli=target_mysql_client_cli,
                    host=target.host(),
                    port=target.port(),
                    admin_user=User(
                        target.user(),
                        target.password()
                    ),
                ),
                source_name=database_info.source_database_name,
                target_name=database_info.target_database_name,
                users=users,
            )
        else:
            # we do not copy MySQL databases from Windows to Linux and vice
            # versa
            return None

    @staticmethod
    def _get_mssql_database_object(database_info_source, subscription, database_info):
        """Initialize and return MSSQLDatabase object

        :type database_info: parallels.core.utils.database_utils.DatabaseInfo
        """

        source = database_info.source_database_server
        target = database_info.target_database_server

        def get_session_file_path(filename, target_node=target):
            return target_node.get_session_file_path(filename)

        users = database_info_source.get_db_users(
            subscription.name,
            database_info.source_database_name,
            source.type()
        )

        return MSSQLDatabase(
            source_name=database_info.source_database_name,
            target_name=database_info.target_database_name,
            source_access=MSSQLDatabaseSourceAccess(
                host=get_mssql_host_for_target_server(source),
                admin_user=User(source.user(), source.password()),
            ),
            target_access=MSSQLDatabaseTargetAccess(
                host=target.host(),
                admin_user=User(target.user(), target.password()),
            ),
            source_server=source.panel_server,
            target_server=target.panel_server,
            users=users,
        )

    @staticmethod
    def _get_pgsql_database_object(database_info_source, subscription, database_info):
        """Initialize and return PostgreSQLDatabase object

        :type database_info: parallels.core.utils.database_utils.DatabaseInfo
        """

        source = database_info.source_database_server
        target = database_info.target_database_server

        users = database_info_source.get_db_users(
            subscription.name,
            database_info.source_database_name,
            source.type()
        )

        return PostgreSQLDatabase(
            source_name=database_info.source_database_name,
            target_name=database_info.target_database_name,
            source_access=PostgreSQLDatabaseAccess(
                server=source,
                host=source.host(),
                port=source.port(),
                admin_user=User(source.user(), source.password()),
            ),
            target_access=PostgreSQLDatabaseAccess(
                server=target,
                host=target.host(),
                port=target.port(),
                admin_user=User(target.user(), target.password()),
            ),
            users=users,
        )


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

    def list_databases_to_copy(self):
        return group_by(
            self._global_context.migrator._list_databases_to_copy(),
            lambda d: d.subscription_name
        )

    @staticmethod
    def get_source_mysql_client_cli(database_info):
        source = database_info.source_database_server
        if source.is_windows():
            return WindowsMySQLClientCLI(
                source.panel_server,
                source.panel_server.get_path_to_mysql(),
                skip_secure_auth=source.panel_server.mysql_use_skip_secure_auth()
            )
        else:
            return UnixMySQLClientCLI(source)

    def get_db_users(self, subscription_name, db_name, db_type):
        subscription = self._global_context.get_subscription(subscription_name)

        users = []

        for db_user in subscription.database_users:
            if (
                db_user.dbtype == db_type and
                (
                    db_user.raw_dump.database_name == db_name or
                    is_empty(db_user.raw_dump.database_name)
                ) and
                db_user.raw_dump.password is not None
            ):
                users.append(User(
                    login=db_user.target_name,
                    password=db_user.raw_dump.password.password_text,
                    password_type=db_user.raw_dump.password.password_type
                ))

        return users
