from parallels.core.utils.entity import Entity
from parallels.core.utils.json_utils import read_json
from parallels.core.utils.safe_structures import safe_get_list, safe_dict


class SiteMigrationList(Entity):
    def __init__(
        self, filter_applications=None, include_files=None, include_databases=None, task=None
    ):
        """
        :type filter_applications: list[parallels.plesk.source.web.site_migration_list.MigrationListApplication] | None
        :type include_files: list[parallels.plesk.source.web.site_migration_list.MigrationListIncludeFile] | None
        :type include_databases: list[parallels.plesk.source.web.site_migration_list.MigrationListIncludeDatabase] | None
        :type task: str | unicode | None
        """
        self._filter_applications = filter_applications
        self._include_files = include_files
        self._include_databases = include_databases
        self._task = task

    @property
    def filter_applications(self):
        """List of applications to migrate.

        If None is returned, then all applications should be migrated. If list is returned - only applications
        from the list should be migrated.

        :rtype: list[parallels.plesk.source.web.site_migration_list.MigrationListApplication] | None
        """
        return self._filter_applications

    def get_application_filter(self, application_type, path):
        filter_applications = self.filter_applications

        if filter_applications is None:
            return None

        for filter_application in filter_applications:
            if (
                filter_application.source_path == path and
                filter_application.application_type.lower() == application_type.lower()
            ):
                return filter_application

        return None

    @property
    def include_files(self):
        """List of additional files/directories to migrate.

        If None or empty list is returned - no additional files/directories should be migrated.
        Files/directories of subscription detected by migrator are migrated anyway,
        this option does not affect them.

        :rtype: list[parallels.plesk.source.web.site_migration_list.MigrationListIncludeFile] | None
        """
        return self._include_files

    @property
    def include_databases(self):
        """List of additional databases to migrate

        If None or empty list is returned - no additional databases should be migrated.
        Databases of subscription detected by migrator are migrated anyway,
        this option does not affect them.

        :rtype: list[parallels.plesk.source.web.site_migration_list.MigrationListIncludeDatabase] | None
        """
        return self._include_databases

    @property
    def task(self):
        """Task for migration list
        See parallels.plesk.source.web.site_migration_list.Task

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


class MigrationListApplication(Entity):
    """Application to migrate"""

    def __init__(self, application_type, source_path, target_path=None):
        self._application_type = application_type
        self._source_path = source_path
        self._target_path = target_path

    @property
    def application_type(self):
        """
        :rtype: str | unicode
        """
        return self._application_type

    @property
    def source_path(self):
        """
        :rtype: str | unicode
        """
        return self._source_path

    @property
    def target_path(self):
        """
        :rtype: str | unicode | None
        """
        return self._target_path


class MigrationListIncludeFile(Entity):
    """Additional file/directory to migrate"""

    def __init__(self, source_path, target_path):
        self._source_path = source_path
        self._target_path = target_path

    @property
    def source_path(self):
        """
        :rtype: str | unicode
        """
        return self._source_path

    @property
    def target_path(self):
        """
        :rtype: str | unicode
        """
        return self._target_path


class MigrationListIncludeDatabase(Entity):
    """Additional database to migrate"""

    def __init__(self, host, port, username, password, name):
        self._host = host
        self._port = port
        self._username = username
        self._password = password
        self._name = name

    @property
    def host(self):
        """
        :rtype: str | unicode
        """
        return self._host

    @property
    def port(self):
        """
        :rtype: str | unicode
        """
        return self._port

    @property
    def username(self):
        """
        :rtype: str | unicode
        """
        return self._username

    @property
    def password(self):
        """
        :rtype: str | unicode
        """
        return self._password

    @property
    def name(self):
        """
        :rtype: str | unicode
        """
        return self._name


class SiteMigrationListReader(object):
    def read(self, filename):
        """Read site migration list from file

        :type filename: str | unicode
        :rtype: parallels.plesk.source.web.site_migration_list.SiteMigrationList
        """
        data = read_json(filename)
        return SiteMigrationList(
            filter_applications=self._read_filter_applications(data) if data is not None else None,
            include_files=self._read_include_files(data) if data is not None else None,
            include_databases=self._read_include_databases(data) if data is not None else None,
            task=self._read_task(data) if data is not None else None
        )

    @staticmethod
    def _read_filter_applications(subscription_data):
        """
        :type subscription_data: dict
        :rtype: list[parallels.plesk.source.web.site_migration_list.MigrationListApplication] | None
        """
        if 'filter_applications' not in subscription_data:
            return None

        filter_applications = []

        for application_data in safe_get_list(subscription_data, 'filter_applications'):
            application_data = safe_dict(application_data)
            filter_applications.append(
                MigrationListApplication(
                    application_type=application_data.get('type'),
                    source_path=application_data.get('source_path'),
                    target_path=application_data.get('target_path'),
                )
            )

        return filter_applications

    @staticmethod
    def _read_include_files(subscription_data):
        """
        :type subscription_data: dict
        :rtype: list[parallels.plesk.source.web.site_migration_list.MigrationListIncludeFile] | None
        """
        if 'include_files' not in subscription_data:
            return None

        include_files = []

        for include_file_data in safe_get_list(subscription_data, 'include_files'):
            include_file_data = safe_dict(include_file_data)
            include_files.append(
                MigrationListIncludeFile(
                    source_path=include_file_data.get('source_path'),
                    target_path=include_file_data.get('target_path')
                )
            )

        return include_files

    @staticmethod
    def _read_include_databases(subscription_data):
        """
        :type subscription_data: dict
        :rtype: list[parallels.plesk.source.web.site_migration_list.MigrationListIncludeDatabase] | None
        """
        if 'include_databases' not in subscription_data:
            return None

        include_databases = []

        for include_database_data in safe_get_list(subscription_data, 'include_databases'):
            include_database_data = safe_dict(include_database_data)
            include_databases.append(
                MigrationListIncludeDatabase(
                    host=include_database_data.get('host'),
                    port=include_database_data.get('port'),
                    username=include_database_data.get('username'),
                    password=include_database_data.get('password'),
                    name=include_database_data.get('name')
                )
            )

        return include_databases

    @staticmethod
    def _read_task(subscription_data):
        """
        :type subscription_data: dict
        :rtype: str | unicode | None
        """
        return subscription_data.get('task')
