from DateTime import DateTime
from importlib.resources import files
from plone import api
from plone.app.robotframework.testing import AUTOLOGIN_LIBRARY_FIXTURE
from plone.app.testing import IntegrationTesting
from plone.app.testing import MOCK_MAILHOST_FIXTURE
from plone.app.testing import PloneSandboxLayer
from plone.app.testing import setRoles
from plone.app.testing import TEST_USER_ID
from plone.base.interfaces.controlpanel import IMailSchema
from plone.namedfile.file import NamedBlobFile
from plone.registry.interfaces import IRegistry
from plone.testing import zope as zope_testing
from plone.testing.zope import WSGI_SERVER_FIXTURE
from ploneintranet import api as pi_api
from ploneintranet.docconv.client.interfaces import IPreviewSettings
from ploneintranet.docconv.client.utils import getDocumentType
from ploneintranet.docconv.client.utils import PDF_FILENAME
from ploneintranet.notifications.sql import saconfig
from ploneintranet.search.interfaces import IPloneintranetSearchLayer
from ploneintranet.search.solr.testing import load_solr_zcml
from ploneintranet.search.solr.testing import SOLR_FIXTURE
from ploneintranet.search.solr.testing import SolrFunctionalTesting
from ploneintranet.search.solr.testing import SolrIntegrationTesting
from ploneintranet.userprofile.testing import PLONEINTRANET_USERPROFILE_FIXTURE
from unittest import mock
from zope.component import getUtility
from zope.configuration import xmlconfig
from zope.interface import alsoProvides

import os


def mock_generate_pdf(obj, event=None):
    """Just write dummy data as PDF and file previews"""
    doc_type = getDocumentType(obj)
    if not doc_type:
        return

    settings = IPreviewSettings(obj)
    settings.blob_files = {}

    test_content_files_path = (
        files("ploneintranet.suite") / "profiles/testcontent/files"
    )

    if doc_type.requires_conversion:
        settings.blob_files[PDF_FILENAME] = NamedBlobFile(
            data=(test_content_files_path / PDF_FILENAME).read_bytes(),
            filename=PDF_FILENAME,
        )

    settings.blob_files["dump_1.svg"] = NamedBlobFile(
        data=(test_content_files_path / "dump_1.svg").read_bytes(),
        filename="dump_1.svg",
    )

    settings.num_pages = 1
    settings.successfully_converted = True
    settings.converting = False
    settings.last_updated = DateTime().ISO8601()


class PloneIntranetSuiteBaseLayer(PloneSandboxLayer):
    defaultBases = (PLONEINTRANET_USERPROFILE_FIXTURE,)

    # make it easy to use this as a base for customized test layers
    install_profile = "ploneintranet.suite:default"
    uninstall_profile = "ploneintranet.suite:uninstall"

    def setUpZope(self, app, configurationContext):
        import ploneintranet.suite

        self.loadZCML(package=ploneintranet.suite)
        zope_testing.installProduct(app, "collective.workspace")

    def setUpPloneSite(self, portal):
        # setup the default workflow
        portal.portal_workflow.setDefaultChain("simple_publication_workflow")
        self.applyProfile(portal, self.install_profile)
        setRoles(portal, TEST_USER_ID, ["Manager"])

    def tearDownPloneSite(self, portal):
        self.applyProfile(portal, self.uninstall_profile)

    def tearDownZope(self, app):
        # Uninstall product
        zope_testing.uninstallProduct(app, "collective.workspace")


PLONEINTRANET_SUITE_BASE_FIXTURE = PloneIntranetSuiteBaseLayer()

PLONEINTRANET_SUITE_BASE_INTEGRATION_LAYER = IntegrationTesting(
    bases=(PLONEINTRANET_SUITE_BASE_FIXTURE,),
    name="PLONEINTRANET_SUITE_BASE_INTEGRATION",
)


class PloneIntranetSuite(PloneSandboxLayer):
    defaultBases = (SOLR_FIXTURE, PLONEINTRANET_SUITE_BASE_FIXTURE)

    # make it easy to use this as a base for customized test layers
    install_profile = "ploneintranet.suite:testing"

    def setUpZope(self, app, configurationContext):
        # Load resource to disable transitions and animations in robot tests
        import ploneintranet.suite.tests

        self.loadZCML(package=ploneintranet.suite.tests)

        zope_testing.installProduct(app, "collective.workspace")
        load_solr_zcml()

        # Set up a fresh persistent database for notifications
        if os.environ.get("ZOPE_TEST_FOLDER"):
            test_folder = os.environ.get("ZOPE_TEST_FOLDER")
        elif os.getcwd().endswith(os.path.join("parts", "test")):
            test_folder = os.getcwd()
        else:
            test_folder = os.path.join(os.getcwd(), "parts", "test")

        xmlconfig.string(
            f"""
            <configure xmlns="http://namespaces.zope.org/zope"
                xmlns:db="http://namespaces.zope.org/db"
                i18n_domain="untranslated"
                >
                <include
                    package="z3c.saconfig"
                    file="meta.zcml"
                    />
                <db:engine
                    name="ploneintranet_notifications"
                    url="sqlite:///{test_folder}/notifications.sql?check_same_thread=False"
                    />
                <db:session
                    name="ploneintranet_notifications"
                    engine="ploneintranet_notifications"
                    />
            </configure>
            """,
            context=configurationContext,
        )

    def purge_solr(self):
        from ploneintranet.search.solr.interfaces import IMaintenance

        getUtility(IMaintenance).purge()

    def solr_sync(self, portal):
        """Sync the portal content in to Solr"""
        with api.env.adopt_user("admin"):
            request = portal.REQUEST
            alsoProvides(request, IPloneintranetSearchLayer)
            view = api.content.get_view("solr-maintenance", portal, request)
            view.sync(close_connection=True)

    def setUpPloneSite(self, portal):
        """Configure the site with testcontent.

        NB trying to reset the notifications database here via
        session.rollback() or dropping and re-creating the tables, will mess
        up transaction isolation between test layers in very obscure ways that
        will surface only when specific sequences of test layer setup/teardown
        are triggered (esp if ploneintranet.workspace layers get sorted to run
        before ploneintranet.suite layers).

        Instead, we rely on disabling notifications event handlers during site
        setup (see above), plus wiping the tables at the start of individual
        notifications tests.
        """
        self.purge_solr()

        # Start with a clean notifications database.
        session = saconfig.session()
        saconfig.Base.metadata.drop_all(session.bind.engine)
        saconfig.Base.metadata.create_all(session.bind.engine)

        # Install into Plone site using portal_setup
        with mock.patch(
            "ploneintranet.api.previews.generate_pdf", side_effect=mock_generate_pdf
        ):
            with pi_api.events.notifications_disabled:
                self.applyProfile(portal, self.install_profile)

        self.solr_sync(portal)
        SOLR_FIXTURE._solr_snapshot()
        registry = getUtility(IRegistry)
        mail_settings = registry.forInterface(IMailSchema, prefix="plone", check=False)
        mail_settings.smtp_host = "localhost"
        mail_settings.email_from_address = "quaive@localhost"
        portal.restrictedTraverse("pas-graph-init")()

    def tearDownPloneSite(self, portal):
        self.purge_solr()


PLONEINTRANET_SUITE = PloneIntranetSuite()

PLONEINTRANET_SUITE_INTEGRATION = IntegrationTesting(
    bases=(PLONEINTRANET_SUITE,), name="PLONEINTRANET_SUITE_INTEGRATION"
)

PLONEINTRANET_SUITE_SOLR_INTEGRATION = SolrIntegrationTesting(
    bases=(PLONEINTRANET_SUITE,), name="PLONEINTRANET_SUITE_SOLR_INTEGRATION"
)

PLONEINTRANET_SUITE_FUNCTIONAL = SolrFunctionalTesting(
    bases=(PLONEINTRANET_SUITE,), name="PLONEINTRANET_SUITE_FUNCTIONAL"
)

PLONEINTRANET_SUITE_ROBOT = SolrFunctionalTesting(
    bases=(
        PLONEINTRANET_SUITE,
        MOCK_MAILHOST_FIXTURE,
        AUTOLOGIN_LIBRARY_FIXTURE,
        WSGI_SERVER_FIXTURE,
    ),
    name="PLONEINTRANET_SUITE_ROBOT",
)
