from parallels.core import MigrationError
from parallels.core.logging import get_logger
from parallels.core.hosting_repository.customer import CustomerModel, CustomerEntity
from parallels.core.registry import Registry
from parallels.core.utils.common import join_nonempty_strs, safe_string_repr
from parallels.core.utils.mysql import escape_args_list
from parallels.plesk import messages
from parallels.plesk.hosting_repository.base import PleskBaseModel
from parallels.plesk.hosting_repository.utils.cli.client import ClientDisableCli, ClientExternalIdCli
from parallels.plesk.hosting_repository.utils.cli.client_pref import ClientPrefUpdateLocaleCli
from parallels.plesk.hosting_repository.utils.cli.user import UserUpdateInfoCli
from parallels.plesk.hosting_repository.utils.object_creator import PleskCommandArgumentSimple, \
    PleskPasswordArgument, PleskObjectCreatorAtOnce, PleskObjectCreatorPartial, format_failed_fields_list

logger = get_logger(__name__)


class PleskCustomerModel(CustomerModel, PleskBaseModel):
    def get_list(self, filter_username=None):
        """Retrive list of customers in target panel by given filter

        :type filter_username: list[str]
        :rtype: list[parallels.core.hosting_repository.customer.CustomerEntity]
        """
        if Registry.get_instance().get_context().conn.target.plesk_server.is_power_user_mode:
            # There are no customers in Power User mode
            return []

        if filter_username is not None and len(filter_username) == 0:
            # filter by username is empty, so no one customer could be retrieved
            return []

        customers = []

        query = """
            SELECT
                id, login, pname, email, vendor_id
            FROM
                clients
            WHERE type = "client"
        """
        query_args = {}
        if filter_username is not None:
            filter_username_placeholders, filter_username_values = escape_args_list(filter_username, 'username')
            query += ' AND login in ({filter_username_placeholders_str})'.format(
                filter_username_placeholders_str=', '.join(filter_username_placeholders)
            )
            query_args.update(filter_username_values)

        rows = self.plesk_server.query_panel_db(query, query_args)
        for row in rows:
            customers.append(CustomerEntity(row['id'], row['login'], row['pname'], '', row['email'], row['vendor_id']))

        return customers

    def disable_by_username(self, username):
        """Disable customer with given username in target panel

        :type username: str
        """
        command = ClientDisableCli(self.plesk_cli_runner, username)
        command.run()

    def create(self, owner_username, customer):
        """Create given customer in target panel; return id of created customer and list of detected errors

        :type owner_username: str | unicode | None
        :type customer: parallels.core.target_data_model.Client
        :rtype: int, list[str]
        """
        customer_command_arguments = [
            PleskCommandArgumentSimple('company', 'company', lambda _customer: _customer.company),
            PleskCommandArgumentSimple('contact name', 'name', lambda _customer: join_nonempty_strs([
                _customer.personal_info.first_name,
                _customer.personal_info.last_name
            ])),
            PleskCommandArgumentSimple('e-mail', 'email', lambda _customer: _customer.personal_info.email),
            PleskCommandArgumentSimple('address', 'address', lambda _customer: _customer.personal_info.address),
            PleskCommandArgumentSimple('city', 'city', lambda _customer: _customer.personal_info.city),
            # Note: country must prepend state and ZIP code, because Plesk validation rules for these fields
            # depend on country
            PleskCommandArgumentSimple('country', 'country', lambda _customer: _customer.personal_info.country_code),
            PleskCommandArgumentSimple('state', 'state', lambda _customer: _customer.personal_info.state),
            PleskCommandArgumentSimple('ZIP code', 'zip', lambda _customer: _customer.personal_info.postal_code),
            PleskCommandArgumentSimple('phone', 'phone', lambda _customer: _customer.personal_info.primary_phone),
            PleskCommandArgumentSimple('fax', 'fax', lambda _customer: _customer.personal_info.fax),
            PleskCommandArgumentSimple('creation date', 'creation-date', lambda _customer: _customer.creation_date),
            PleskCommandArgumentSimple('description', 'description', lambda _customer: _customer.description),
            PleskPasswordArgument()
        ]

        warnings = []
        additional_info_fields = None
        static_args = []
        if owner_username is not None:
            static_args.extend(['-owner', owner_username])

        try:
            creator = PleskObjectCreatorAtOnce('client', customer_command_arguments, static_args)
            creator.create(customer)
        except Exception:
            logger.exception()
            creator = PleskObjectCreatorPartial('client', customer_command_arguments, static_args)
            failed_fields = creator.create(customer)

            if len(failed_fields) > 0:
                additional_info_fields, error_message_fields = format_failed_fields_list(failed_fields, customer)
                warnings.append(messages.FAILED_TO_SET_CUSTOMER_FIELDS.format(
                    login=customer.login, fields=error_message_fields
                ))

        customer_entity = self.get_by_username(customer.login)
        if customer_entity is None:
            raise MigrationError(messages.FAILED_TO_GET_CUSTOMER_ID.format(login=customer.login))

        # Update customer's locate as separate action - so missing locales do not block migration
        self._update_locale(customer, warnings)

        self._update_info(customer.login, customer.personal_info, warnings, additional_info_fields)

        return customer_entity.customer_id, warnings

    def set_external_id(self, username, external_id):
        """Set External ID for given customer in target Plesk

        :type username: str
        :type external_id: str
        """
        command = ClientExternalIdCli(self.plesk_cli_runner, username, external_id)
        command.run()

    def _update_locale(self, customer, warnings):
        """Update locale of given customer in target Plesk

        :type customer: parallels.core.target_data_model.Client
        :type warnings: list[str]
        """
        if customer.personal_info.locale is None:
            return

        try:
            command = ClientPrefUpdateLocaleCli(self.plesk_cli_runner, customer.login, customer.personal_info.locale)
            command.run()
        except Exception as e:
            warnings.append(messages.IMPORT_SET_LOCALE_FAILED.format(
                locale=customer.personal_info.locale,
                error=safe_string_repr(e).strip()
            ))

    def _update_info(self, username, personal_info, warnings, append_to_additional_info=None):
        """Update IM, IM type and comment fields for user (reseller, client)

        If append_additional_info field is set, this data is appended to the end of comment field.

        :type username: str|unicode
        :type personal_info: parallels.core.target_data_model.PersonalInfo
        :type warnings: list[str|unicode]
        :type append_to_additional_info: str | unicode | None
        """
        try:
            command = UserUpdateInfoCli(self.plesk_cli_runner, username, personal_info, append_to_additional_info)
            command.run()
        except Exception as e:
            message = messages.FAILED_TO_SET_CUSTOMER_CONTACT_DATA
            warnings.append(message.format(login=username, reason=safe_string_repr(e).strip()))
