import posixpath

from parallels.core.logging import get_logger
from parallels.core.runners.base import BaseRunner
from parallels.core.runners.entities import ExecutionOptions
from parallels.core.utils.common import cached
from parallels.core.utils.common_constants import PASSWD_FIELD_LOGIN, PASSWD_FIELD_GID, PASSWD_FIELD_UID
from parallels.core.utils.paths import web_paths
from parallels.core.utils.paths.copy_web_content import CopyWebContentItem
from parallels.core.utils.paths.web_files import BaseWebFiles

logger = get_logger(__name__)


class PPCPLWebFiles(BaseWebFiles):
    def list_files_to_copy(self, global_context, subscription):
        """Make a list of source server directories and files to be transferred.

        :type global_context: parallels.plesk.source.ppcpl.global_context.PPCPLGlobalMigrationContext
        :type subscription: parallels.core.migrated_subscription.MigratedSubscription
        :rtype: list[parallels.core.utils.paths.copy_web_content.CopyWebContentItem]
        """
        return (
            self._list_main_domain_web_files_to_copy(global_context, subscription) +
            self._list_subdomain_files_to_copy(global_context, subscription) +
            self._list_anonftp_files_to_copy(global_context, subscription) +
            self._list_webuser_files_to_copy(global_context, subscription)
        )

    def _list_main_domain_web_files_to_copy(self, global_context, subscription):
        """List httpdocs files of main domain - www-root and cgi-bin

        :type global_context: parallels.plesk.source.ppcpl.global_context.PPCPLGlobalMigrationContext
        :type subscription: parallels.core.migrated_subscription.MigratedSubscription
        :rtype: list[parallels.core.utils.paths.copy_web_content.CopyWebContentItem]
        """
        tocopy = []

        vhost_name = subscription.name.encode('idna')
        ppcpl_vhost_fs_root = global_context.get_domain_filesystem_root(vhost_name)

        source_www_root = '%s/var/www/html' % ppcpl_vhost_fs_root

        tocopy.append(
            CopyWebContentItem(
                source_path=web_paths.AbsolutePath(source_www_root),
                target_path=web_paths.WebspaceDocumentRoot(subscription.converted_dump),
                exclude=self._get_htaccess_files_to_exclude(global_context, source_www_root),
                skip_if_source_not_exists=False,
                source_system_user_login=self._get_source_sysuser_login(subscription),
            ),
        )

        source_cgi_root = '%s/var/www/cgi-bin' % ppcpl_vhost_fs_root

        tocopy.append(
            CopyWebContentItem(
                source_path=web_paths.AbsolutePath(source_cgi_root),
                target_path=web_paths.WebspaceCGIBin(subscription.converted_dump),
                exclude=self._get_htaccess_files_to_exclude(global_context, source_cgi_root),
                skip_if_source_not_exists=True,
                source_system_user_login=self._get_source_sysuser_login(subscription),
            )
        )

        return tocopy

    def _list_subdomain_files_to_copy(self, global_context, subscription):
        """List httpdocs files of all subdomains - www-root and cgi-bin to copy

        :type global_context: parallels.plesk.source.ppcpl.global_context.PPCPLGlobalMigrationContext
        :type subscription: parallels.core.migrated_subscription.MigratedSubscription
        :rtype: list[parallels.core.utils.paths.copy_web_content.CopyWebContentItem]
        """
        tocopy = []

        vhost_name = subscription.name.encode('idna')
        ppcpl_vhost_fs_root = global_context.get_domain_filesystem_root(vhost_name)

        subdomain_dirs = global_context.dump_agent.get_domain_subdomain_dirs(subscription.name)
        for subdomain in subscription.converted_dump.iter_subdomains():
            # note that subdomains don't have protected directories management
            # so there is no need to exclude .htaccess files like we exclude
            # them for domains
            subdomain_www_root, subdomain_cgi_root = subdomain_dirs[
                subdomain.short_name.encode('idna')
            ]
            tocopy.append(
                CopyWebContentItem(
                    source_path=web_paths.AbsolutePath(
                        '%s/%s' % (ppcpl_vhost_fs_root, subdomain_www_root)
                    ),
                    target_path=web_paths.SiteDocumentRoot(subscription.converted_dump, subdomain),
                    skip_if_source_not_exists=True,
                    source_system_user_login=self._get_source_sysuser_login(subscription),
                ),
            )

            if subdomain_cgi_root != '':
                tocopy.append(
                    CopyWebContentItem(
                        source_path=web_paths.AbsolutePath(
                            '%s/%s' % (ppcpl_vhost_fs_root, subdomain_cgi_root)
                        ),
                        target_path=web_paths.SiteCGIBin(subscription.converted_dump, subdomain),
                        skip_if_source_not_exists=True,
                        source_system_user_login=self._get_source_sysuser_login(subscription),
                    ),
                )

        return tocopy

    def _list_anonftp_files_to_copy(self, global_context, subscription):
        """
        :type global_context: parallels.plesk.source.ppcpl.global_context.PPCPLGlobalMigrationContext
        :type subscription: parallels.core.migrated_subscription.MigratedSubscription
        :rtype: list[parallels.core.utils.paths.copy_web_content.CopyWebContentItem]
        """
        tocopy = []

        vhost_name = subscription.name.encode('idna')
        ppcpl_vhost_fs_root = global_context.get_domain_filesystem_root(vhost_name)

        if subscription.raw_dump.anonftp_enabled:
            tocopy.append(
                CopyWebContentItem(
                    source_path=web_paths.AbsolutePath('%s/var/ftp/pub' % ppcpl_vhost_fs_root),
                    target_path=web_paths.WebspaceAnonFTPPub(subscription.converted_dump),
                    skip_if_source_not_exists=True,
                    source_system_user_login=self._get_source_sysuser_login(subscription),
                )
            )

            tocopy.append(
                CopyWebContentItem(
                    source_path=web_paths.AbsolutePath('%s/var/ftp/uploads' % ppcpl_vhost_fs_root),
                    target_path=web_paths.WebspaceAnonFTPIncoming(subscription.converted_dump),
                    skip_if_source_not_exists=True,
                    source_system_user_login=self._get_source_sysuser_login(subscription),
                )
            )

        return tocopy

    def _list_webuser_files_to_copy(self, global_context, subscription):
        """
        :type global_context: parallels.plesk.source.ppcpl.global_context.PPCPLGlobalMigrationContext
        :type subscription: parallels.core.migrated_subscription.MigratedSubscription
        :rtype: list[parallels.core.utils.paths.copy_web_content.CopyWebContentItem]
        """
        tocopy = []

        vhost_name = subscription.name.encode('idna')
        ppcpl_vhost_fs_root = global_context.get_domain_filesystem_root(vhost_name)
        webuser_info = self._get_chroot_system_user_info(global_context, subscription)

        for webuser_name in subscription.raw_dump.get_phosting_webuser_names():
            # Files of web user are owned by corresponding system user in chrooted environment,
            # get UID and GID of that system user so file copy utility works under that user
            # on the source server
            if webuser_name in webuser_info:
                uid, gid = webuser_info[webuser_name]
            else:
                uid = None
                gid = None

            tocopy.append(
                CopyWebContentItem(
                    source_path=web_paths.AbsolutePath(
                        '%s/home/%s/public_html' % (ppcpl_vhost_fs_root, webuser_name)
                    ),
                    target_path=web_paths.WebspaceWebUser(subscription.converted_dump, webuser_name),
                    skip_if_source_not_exists=True,
                    source_system_user_uid=uid,
                    source_system_user_gid=gid
                )
            )
        return tocopy

    @staticmethod
    def _get_htaccess_files_to_exclude(global_context, path):
        """Get list of .htaccess files that should not be copied to target node

        In PPCPL, a .htaccess file becomes owned by root after site admin
        protects appropriate directory using CP. Such protected directories we
        report to Plesk separately. If we don't exclude them here, Plesk can
        have problems importing such site - unable to remove these .htaccess
        files at restoration stage.

        :type global_context: parallels.plesk.source.ppcpl.global_context.PPCPLGlobalMigrationContext
        :type path: str | unicode
        :rtype: list[str | unicode]
        """
        with global_context.conn.ppcpl.runner() as runner:
            assert isinstance(runner, BaseRunner)

            if not runner.file_exists(path):
                return []

            output = runner.execute_command(
                'find {path} -name .htaccess -type f -user root -printf "%P\n"',
                dict(path=path),
                ExecutionOptions(ignore_exit_code=True)
            ).stdout

            output = output.strip()
            if output == '':
                # split returns list of one empty string,
                # if source string was empty, so handle this situation
                return []
            else:
                return output.split("\n")

    @cached
    def _get_source_sysuser_login(self, subscription):
        """Get login of system user which owns files on the source server

        In PPCPL files are stored in chroot environments.
        System user name that is seen by end user (and stored at /etc/passwd of chrooted environment)
        differs from system user name at global level (at server's /etc/passwd).
        To copy files correctly, we should use the name at global level, not in chrooted environment.

        :type subscription: parallels.core.migrated_subscription.MigratedSubscription
        :rtype: str | unicode | None
        """
        with subscription.web_source_server.runner() as runner:
            assert isinstance(runner, BaseRunner)
            site_id = runner.execute_command(
                '''psql appldb -qt -c "SELECT site_id FROM siteinfo WHERE domain = '{subscription_name}'"'''.format(
                    subscription_name=subscription.name_idn
                )
            ).stdout.strip()

        return "admin%s" % site_id

    @staticmethod
    def _get_chroot_system_user_info(global_context, subscription):
        """Get UID and GID of system users in chrooted environment

        :type global_context: parallels.plesk.source.ppcpl.global_context.PPCPLGlobalMigrationContext
        :type subscription: parallels.core.migrated_subscription.MigratedSubscription
        :rtype: dict[str | unicode, tuple]
        """
        vhost_name = subscription.name_idn
        ppcpl_vhost_fs_root = global_context.get_domain_filesystem_root(vhost_name)

        with subscription.web_source_server.runner() as runner:
            assert isinstance(runner, BaseRunner)
            passwd_file = runner.get_file_contents(posixpath.join(ppcpl_vhost_fs_root, "etc", "passwd"))
            passwd_file_items = [
                line.split(':') for line in passwd_file.split("\n") if line.strip() != ""
            ]

            return {
                item[PASSWD_FIELD_LOGIN]: (item[PASSWD_FIELD_UID], item[PASSWD_FIELD_GID])
                for item in passwd_file_items
                if len(item) > max(PASSWD_FIELD_LOGIN, PASSWD_FIELD_GID, PASSWD_FIELD_UID)
            }
