import random
import string
import time

from parallels.core import MigrationNoRepeatError
from parallels.core.hosting_repository.subscription import SubscriptionModel
from parallels.core.logging import get_logger
from parallels.core.utils.common import generate_random_password, safe_format, is_empty, format_list
from parallels.plesk.hosting_repository.subscription import PleskSubscriptionModel
from parallels.plesk.utils.xml_rpc.plesk.operator import SubscriptionOperator, SubscriptionAddInfo, \
    SubscriptionAddGenSetup, SubscriptionHostingVirtual, SubscriptionHostingNone, SubscriptionAddHostingType
from parallels.plesk.utils.xml_rpc.plesk.operator.extension import ExtensionCallOperator, TaskListInfo
from parallels.plesk_multi_server import messages
from parallels.plesk_multi_server.hosting_repository.base import PleskMultiServerBaseModel
from parallels.plesk_multi_server.target_data_model import PleskMultiServerTargetSubscriptionData

logger = get_logger(__name__)


class PleskMultiServerSubscriptionModel(SubscriptionModel, PleskMultiServerBaseModel):
    CREATE_MAX_POLL_ATTEMPTS = 60 * 60  # 1 hour
    CREATE_POLL_INTERVAL = 1  # 1 second

    def get_list(self, filter_name=None, filter_owner_id=None):
        """Retrieve list of subscription from target panel by given filter

        :type filter_name: list[str] | None
        :type filter_owner_id: list[int] | None
        :rtype: list[parallels.core.hosting_repository.subscription.SubscriptionEntity]
        """
        return PleskSubscriptionModel(self.pms_management_server).get_list(
            filter_name, filter_owner_id
        )

    def is_exists(self, subscription_name, guid=None):
        """Check if subscription with given name or guid exists in target panel

        :type subscription_name: str
        :type guid: str | None
        :rtype: bool
        """
        return PleskSubscriptionModel(self.pms_management_server).is_exists(
            subscription_name, guid
        )

    def create(self, subscription, owner, reseller, is_enable_hosting):
        """Create given subscription in target panel
        
       Returns list of non-critical errors (warnings).

        :type subscription: parallels.core.target_data_model.Subscription
        :type owner: parallels.core.target_data_model.Client | parallels.core.target_data_model.Reseller
        :type reseller: parallels.core.target_data_model.Reseller
        :type is_enable_hosting: bool
        :rtype: list[str | unicode]
        """
        target_data = subscription.target_data
        if isinstance(target_data, PleskMultiServerTargetSubscriptionData):
            node_ip = target_data.node_ip
            ipv4_type = target_data.ipv4_type
            ipv6_type = target_data.ipv6_type
        else:
            raise TypeError()

        pms_api = self.pms_management_server.plesk_multi_server_api()

        random_username = 'sub_%s' % ''.join(random.choice(string.digits) for _ in range(10))
        if subscription.sysuser_login is None:
            actual_subscription_username = random_username
        else:
            actual_subscription_username = subscription.sysuser_login

        subscription_create_data = pms_api.send_multi_server(
            SubscriptionOperator.Add(SubscriptionAddInfo(
                gen_setup=SubscriptionAddGenSetup(
                    name=subscription.name,
                    owner_id=None,
                    owner_login=owner.login,
                    htype=SubscriptionAddHostingType.VIRTUAL if is_enable_hosting else None,
                    ip_address=None,
                ),
                hosting=SubscriptionHostingVirtual(
                    properties=[
                        ('ftp_login', actual_subscription_username),
                        ('ftp_password', generate_random_password())
                    ],
                    ip_addresses=None,
                ) if is_enable_hosting else SubscriptionHostingNone(),
                plan_name=subscription.plan_name,
            )),
            ipv4=ipv4_type,
            ipv6=ipv6_type,
            node_ip=node_ip
        ).data

        subscription_id = subscription_create_data.subscription_id

        all_tasks = []
        finished_tasks = []

        for attempt in range(0, self.CREATE_MAX_POLL_ATTEMPTS):
            all_tasks = pms_api.send_multi_server(
                ExtensionCallOperator.PleskMultiServer.TaskListOperator(filter_subscription_ids=[subscription_id])
            ).data
            """:type: list[parallels.plesk.utils.xml_rpc.plesk.operator.extension.TaskListInfo]"""

            if len(all_tasks) > 0:
                finished_tasks = [
                    task for task in all_tasks if task.status == TaskListInfo.TASK_STATUS_DONE
                ]
                failed_tasks = [
                    task for task in all_tasks if task.status == TaskListInfo.TASK_STATUS_ERROR
                ]
                blocked_tasks = [
                    task for task in all_tasks if task.status == TaskListInfo.TASK_STATUS_BLOCKED
                ]

                if len(failed_tasks) > 0:
                    logger.fdebug(
                        messages.DEBUG_PROVISIONING_FAILED,
                        tasks=format_list([task.task_id for task in failed_tasks])
                    )
                    failure_messages = []
                    for task in failed_tasks:
                        if not is_empty(task.status_message):
                            failure_messages.append(safe_format(
                                messages.ERROR_PROVISIONING_FAILED_WITH_REASON,
                                reason=task.status_message
                            ))

                    if len(failure_messages) > 0:
                        message = '\n'.join(failure_messages)
                    else:
                        message = messages.ERROR_PROVISIONING_FAILED_WITHOUT_REASON

                    raise MigrationNoRepeatError(message)
                elif len(blocked_tasks) > 0:
                    logger.fdebug(
                        messages.DEBUG_PROVISIONING_BLOCKED,
                        tasks=format_list([task.task_id for task in failed_tasks])
                    )
                    raise MigrationNoRepeatError(messages.ERROR_PROVISIONING_FAILED_BLOCKED_TASK)
                elif len(finished_tasks) == len(all_tasks):
                    logger.fdebug(messages.DEBUG_PROVISIONING_FINISHED)
                    break
                else:
                    logger.fdebug(messages.DEBUG_PROVISIONING_IN_PROGRESS)

            time.sleep(self.CREATE_POLL_INTERVAL)

        if len(all_tasks) == 0 or len(finished_tasks) != len(all_tasks):
            raise MigrationNoRepeatError(
                safe_format(
                    messages.ERROR_PROVISIONING_HANGED,
                    time=self.CREATE_MAX_POLL_ATTEMPTS * self.CREATE_POLL_INTERVAL
                )
            )

        try:
            server = self.get_subscription_service_server(subscription.name)

            # Remove 'httpdocs' directory created by default virtual host template.
            # There is no way to specify skip virtual host template by Plesk API, so it is always created
            # and contains some garbage, which could make site broken after migration. For example, by default
            # it contains file "index.htm" with default site page, and if site on the source server had "index.php"
            # file, the site won't work after migration as expected:
            # "index.htm" will be returned instead of "index.php".
            with server.runner() as runner:
                runner.remove_directory(
                    server.join_file_path(server.get_vhost_dir(subscription.name), 'httpdocs')
                )
        except Exception as e:
            logger.exception()
            return [safe_format(messages.FAILED_TO_CLEAN_UP_VIRTUAL_HOST, reason=e)]

    def set_username(self, subscription_name, username):
        """Set username of system user associated with given subscription in target panel

        :type subscription_name: str
        :type username: str
        """
        return PleskSubscriptionModel(self.pms_management_server).set_username(
            subscription_name, username
        )

    def set_owner(self, subscription_name, owner_username):
        """Set owner of subscription with given name in target panel

        :type subscription_name: str
        :type owner_username: str
        """
        return PleskSubscriptionModel(self.pms_management_server).set_owner(
            subscription_name, owner_username
        )

    def set_limits(
        self, subscription_name,
        max_domains=None, max_subdomains=None, max_domain_aliases=None, max_databases=None,
        max_mssql_databases=None,
        max_mail_accounts=None
    ):
        """Set limits for given subscription

        :type subscription_name: str
        :type max_domains: int | None
        :type max_subdomains: int | None
        :type max_domain_aliases: int | None
        :type max_databases: int | None
        :type max_mssql_databases: int | None
        :type max_mail_accounts: int | None
        """
        return PleskSubscriptionModel(self.pms_management_server).set_limits(
            subscription_name,
            max_domains, max_subdomains, max_domain_aliases, max_databases,
            max_mssql_databases,
            max_mail_accounts
        )

    def set_external_id(self, subscription_name, external_id):
        """Set External ID for given subscription in target panel

        :type subscription_name: str
        :type external_id: str
        """
        return PleskSubscriptionModel(self.pms_management_server).set_external_id(
            subscription_name, external_id
        )

    def enable_virtual_hosting(self, subscription):
        """Enable virtual hosting for subscription with given name in target panel

        :type subscription: parallels.core.target_data_model.Subscription
        """
        return PleskSubscriptionModel(self.pms_management_server).enable_virtual_hosting(subscription)

    def sync(self, subscription_name):
        """Sync subscription with given name with associated service plan in target panel

        :type subscription_name: str
        """
        return PleskSubscriptionModel(self.pms_management_server).sync(
            subscription_name
        )

    def get_dedicated_application_pool_user(self, subscription_name):
        """Retrieve name of dedicated application pool user for given subscription on target panel

        :type subscription_name: str
        """
        return PleskSubscriptionModel(self.pms_management_server).get_dedicated_application_pool_user(
            subscription_name
        )

    def update_security(self, subscription_name):
        """Actualize security metadata for given subscription in target panel

        :type subscription_name: str
        """
        return PleskSubscriptionModel(self.pms_management_server).update_security(
            subscription_name
        )

    def remove(self, subscription_name):
        """Remove subscription from the server

        :type subscription_name: str | unicode
        """
        return PleskSubscriptionModel(self.pms_management_server).remove(
            subscription_name
        )
