from AccessControl import Unauthorized
from importlib.resources import files
from io import BytesIO
from plone import api
from plone.namedfile import NamedBlobImage
from ploneintranet import api as pi_api
from ploneintranet.testing import DummyMessage
from ploneintranet.userprofile.browser.tiles.contacts_search import ContactsSearch
from ploneintranet.userprofile.browser.userprofile import AuthorView
from ploneintranet.userprofile.browser.userprofile import AvatarsView
from ploneintranet.userprofile.browser.userprofile import default_avatar
from ploneintranet.userprofile.browser.userprofile import MyAvatar
from ploneintranet.userprofile.browser.userprofile import MyProfileView
from ploneintranet.userprofile.browser.userprofile import UserProfileView
from ploneintranet.userprofile.testing import (
    PLONEINTRANET_USERPROFILE_INTEGRATION_TESTING,
)
from ploneintranet.userprofile.tests.base import BaseTestCase
from unittest import mock
from zExceptions import NotFound
from ZPublisher.HTTPRequest import FileUpload

import quopri

TEST_AVATAR_FILENAME = "test_avatar.jpg"


class TestUserProfileBase(BaseTestCase):
    layer = PLONEINTRANET_USERPROFILE_INTEGRATION_TESTING

    def setUp(self):
        super().setUp()
        self.login_as_portal_owner()
        username1 = "johndoe"
        self.profile1 = api.content.create(
            container=self.profiles,
            type="ploneintranet.userprofile.userprofile",
            id=username1,
            username=username1,
            first_name="John",
            last_name="Doe",
        )
        api.content.transition(self.profile1, "approve")
        self.profile1.reindexObject()

        username2 = "janedoe"
        self.profile2 = api.content.create(
            container=self.profiles,
            type="ploneintranet.userprofile.userprofile",
            id=username2,
            username=username2,
            first_name="Jane",
            last_name="Doe",
        )
        api.content.transition(self.profile2, "approve")
        self.profile2.reindexObject()

        self.logout()

    def create_test_file_field(self, data, filename):
        return FileUpload(
            mock.Mock(
                filename=filename,
                file=BytesIO(data),
                headers={},
                name="foo",
            )
        )


class TestUserProfileView(TestUserProfileBase):
    def test_profile_container(self):
        """We want self.profiles, the profiles container to be public"""
        self.assertEqual(
            self.profiles.portal_type, "ploneintranet.userprofile.userprofilecontainer"
        )
        self.assertEqual(api.content.get_state(self.profiles), "published")

    def test_source_field(self):
        self.assertEqual(self.portal.profiles.johndoe.source, "plone")
        with api.env.adopt_user("admin"):
            new = api.content.create(
                container=self.profiles,
                type="ploneintranet.userprofile.userprofile",
                id="foo",
                username="foo",
                source="ldap",
            )
        self.assertEqual(new.source, "ldap")

    def test_is_me(self):
        profile_view = UserProfileView(self.profile1, self.request)

        self.login(self.profile1.username)
        self.assertTrue(profile_view.is_me())
        self.logout()

        self.login(self.profile2.username)
        self.assertFalse(profile_view.is_me())
        self.logout()

    def test__user_details(self):
        self.login(self.profile1.username)
        profile_view = UserProfileView(self.profile1, self.request)
        details = profile_view._user_details(
            [self.profile1.username, self.profile2.username]
        )
        self.assertEqual(len(details), 2)
        self.assertEqual(details[0].Title(), self.profile1.fullname)
        self.assertEqual(details[1].Title(), self.profile2.fullname)

    def test_recent_contacts_not_duplicated(self):
        self.login(self.profile1.username)
        profile_view = UserProfileView(self.profile2, self.request)
        profile_view._update_recent_contacts()
        self.assertIn(self.profile2.username, self.profile1.recent_contacts or [])

        profile_view._update_recent_contacts()
        recent = self.profile1.recent_contacts or []
        self.assertEqual(
            recent.count(self.profile2.username), 1, "Recent contact entry duplicated"
        )

    def test_recent_contacts_most_recent_first(self):
        self.profile1.recent_contacts = ["some_user"]
        self.login(self.profile1.username)
        profile_view = UserProfileView(self.profile2, self.request)
        profile_view._update_recent_contacts()
        recent = self.profile1.recent_contacts or []
        self.assertEqual(recent[0], self.profile2.username)

    def test_recent_contacts_self_excluded(self):
        self.login(self.profile1.username)
        profile_view = UserProfileView(self.profile1, self.request)
        profile_view._update_recent_contacts()
        self.assertNotIn(self.profile1.username, self.profile1.recent_contacts or [])

    def test_recent_contacts_length_limited(self):
        self.profile1.recent_contacts = [f"user{n}" for n in range(20)]
        self.login(self.profile1.username)
        profile_view = UserProfileView(self.profile2, self.request)
        profile_view._update_recent_contacts()
        recent = self.profile1.recent_contacts or []
        self.assertLessEqual(len(recent), 20)

    def test_userprofile_tabs_default(self):
        view = api.content.get_view(
            "userprofile-view", self.profile2, self.request.clone()
        )
        with api.env.adopt_user("johndoe"):
            with mock.patch.multiple(
                "ploneintranet.userprofile.browser.userprofile.UserProfileView",
                following=mock.Mock(return_value=["your-name"]),
                followers=mock.Mock(return_value=["your-name"]),
            ):
                self.assertEqual(len(view.tabs), 5)
                self.assertEqual(view.tabs[0]["id"], "userprofile-info")
                self.assertListEqual(
                    view.get_tab_css_classes(view.tabs[0]).split(),
                    ["pat-inject", "userprofile-tab-userprofile-info", "current"],
                )
                self.assertListEqual(
                    view.get_tab_css_classes(view.tabs[1]).split(),
                    ["pat-inject", "userprofile-tab-userprofile-posts"],
                )

    def test_userprofile_tabs_other_view(self):
        view = api.content.get_view(
            "userprofile-posts", self.profile2, self.request.clone()
        )
        with api.env.adopt_user("johndoe"):
            with mock.patch.multiple(
                "ploneintranet.userprofile.browser.userprofile.UserProfileView",
                following=mock.Mock(return_value=["your-name"]),
                followers=mock.Mock(return_value=["your-name"]),
            ):
                self.assertEqual(len(view.tabs), 5)
                self.assertEqual(view.tabs[0]["id"], "userprofile-info")
                self.assertListEqual(
                    view.get_tab_css_classes(view.tabs[0]).split(),
                    ["pat-inject", "userprofile-tab-userprofile-info"],
                )
                self.assertListEqual(
                    view.get_tab_css_classes(view.tabs[1]).split(),
                    ["pat-inject", "userprofile-tab-userprofile-posts", "current"],
                )

    def test_userprofile_tabs_follow_condition(self):
        view = api.content.get_view(
            "userprofile-info", self.profile2, self.request.clone()
        )
        with api.env.adopt_user("johndoe"):
            ids = [tab["id"] for tab in view.tabs]
            self.assertEqual(len(view.tabs), 3)
            self.assertEqual(view.tabs[0]["id"], "userprofile-info")
            self.assertListEqual(
                view.get_tab_css_classes(view.tabs[0]).split(),
                ["pat-inject", "userprofile-tab-userprofile-info", "current"],
            )
            self.assertListEqual(
                view.get_tab_css_classes(view.tabs[1]).split(),
                ["pat-inject", "userprofile-tab-userprofile-posts"],
            )
            self.assertNotIn("userprofile-following", ids)
            self.assertNotIn("userprofile-followers", ids)

    def test_userprofile_tabs_customize_portal_actions(self):
        from Products.CMFCore.ActionInformation import Action

        test_action = Action("userprofile-test", title="Test", visible=True)
        portal_actions = api.portal.get_tool("portal_actions")
        portal_actions["userprofile_tabs"]["userprofile-test"] = test_action

        view = api.content.get_view(
            "userprofile-view", self.profile2, self.request.clone()
        )
        with api.env.adopt_user("johndoe"):
            with mock.patch.multiple(
                "ploneintranet.userprofile.browser.userprofile.UserProfileView",
                following=mock.Mock(return_value=["your-name"]),
                followers=mock.Mock(return_value=["your-name"]),
            ):
                self.assertEqual(len(view.tabs), 6)
                self.assertEqual(view.tabs[-1]["id"], "userprofile-test")

    def test_hidden_fields_userprofile(self):
        self.profile2.industry = "IT"
        self.login(self.profile2.username)

        view = self.profile2.restrictedTraverse("@@userprofile-info")
        view()  # update view
        self.assertTrue("IUserProfileAdditional.industry" in view.fields)

        api.portal.set_registry_record(
            "ploneintranet.userprofile.hidden_fields",
            ("IUserProfileAdditional.industry",),
        )
        view = self.profile2.restrictedTraverse("@@userprofile-info")
        view()  # update view
        self.assertTrue("IUserProfileAdditional.industry" not in view.fields)

        api.portal.set_registry_record(
            "ploneintranet.userprofile.hidden_fields",
            (),
        )
        view = self.profile2.restrictedTraverse("@@userprofile-info")
        view()  # update view
        self.assertTrue("IUserProfileAdditional.industry" in view.fields)


class TestContactsResults(TestUserProfileBase):
    def test_resolve_profile(self):
        self.profile1.recent_contacts = [self.profile2.username]
        self.login(self.profile1.username)
        contacts_results = ContactsSearch(self.portal, self.request)
        recent = contacts_results.recent_contacts()
        self.assertEqual(recent[0], self.profile2)

    def test_invalid_users_dropped(self):
        self.profile1.recent_contacts = ["deleted_user", self.profile2.username]
        self.login(self.profile1.username)
        contacts_results = ContactsSearch(self.portal, self.request)
        recent = contacts_results.recent_contacts()
        self.assertEqual(len(recent), 1)
        self.assertEqual(recent[0], self.profile2)

    def test_length_limited(self):
        # In practice a user will not be in recent_contacts multiple times. We
        # use this shortcut only to avoid creating 10 user profiles.
        self.profile1.recent_contacts = [self.profile2.username] * 11
        self.login(self.profile1.username)
        contacts_results = ContactsSearch(self.portal, self.request)
        recent = contacts_results.recent_contacts()
        self.assertEqual(len(recent), 10)


class TestAuthorView(TestUserProfileBase):
    def test_call(self):
        self.login(self.profile2.username)
        author_view = AuthorView(self.portal, self.request)

        author_view.publishTraverse(self.request, self.profile1.username)
        redirect_url = author_view()
        self.assertEqual(redirect_url, self.profile1.absolute_url())

        author_view.publishTraverse(self.request, "not-a-username")
        with self.assertRaises(NotFound):
            author_view()


class TestMyProfileView(TestUserProfileBase):
    def test_call(self):
        self.login(self.profile2.username)
        myprofile_view = MyProfileView(self.portal, self.request)

        redirect_url = myprofile_view()
        self.assertEqual(redirect_url, self.profile2.absolute_url())

        self.logout()

        with self.assertRaises(Unauthorized):
            myprofile_view()


class TestAvatarViews(TestUserProfileBase):
    def setUp(self):
        super().setUp()
        self.profile1.portrait = NamedBlobImage(
            data=(
                files("ploneintranet.userprofile.tests") / TEST_AVATAR_FILENAME
            ).read_bytes(),
            filename=TEST_AVATAR_FILENAME,
        )
        self.default_avatar = default_avatar(self.request.response)

    def test_avatars_view(self):
        self.login(self.profile1.username)
        avatars_view = AvatarsView(self.portal, self.request)
        avatars_view.publishTraverse(self.request, self.profile1.username)

        data = avatars_view()
        self.assertTrue(len(data) > 0)

        avatars_view.publishTraverse(self.request, self.profile2.username)
        self.assertEqual(avatars_view(), self.default_avatar)

        avatars_view.publishTraverse(self.request, "not-a-username")
        self.assertEqual(avatars_view(), self.default_avatar)

    def test_my_avatar(self):
        self.login(self.profile1.username)
        my_avatar = MyAvatar(self.profile1, self.request)
        data = my_avatar()
        self.assertTrue(len(data) > 0)

        profile_data = my_avatar.avatar_profile()
        self.assertTrue(len(profile_data) > 0)

        avatar = MyAvatar(self.profile2, self.request)

        self.assertEqual(avatar(), self.default_avatar)

    def test_upload_avatar_large(self):
        TEST_AVATAR_FILENAME_LARGE = "test_avatar_large.jpg"
        self.login(self.profile2.username)
        request = self.request.clone()
        request["REQUEST_METHOD"] = "POST"
        request.form["submit"] = "submit"
        request.form["portrait"] = self.create_test_file_field(
            (
                files("ploneintranet.userprofile.tests") / TEST_AVATAR_FILENAME_LARGE
            ).read_bytes(),
            TEST_AVATAR_FILENAME_LARGE,
        )
        view = api.content.get_view("userprofile-view", self.profile2, request)
        view()
        self.assertEqual(self.profile2.portrait.filename, TEST_AVATAR_FILENAME_LARGE)


class TestMailPassword(BaseTestCase):
    layer = PLONEINTRANET_USERPROFILE_INTEGRATION_TESTING

    def setUp(self):
        self.portal = self.layer["portal"]
        self.request = self.layer["request"]
        self.mailhost = self.portal.MailHost
        self.profile = pi_api.userprofile.create(
            "local_user", email="local_user@example.com", approve=True
        )

    def _get_view(self, **params):
        request = self.request.clone()
        request["REQUEST_METHOD"] = "POST"
        request.form.update(params)
        return api.content.get_view("pi-mail-password", self.portal, request)

    def test_local_user(self):
        mail_password = self._get_view(userid="local_user")
        mail_password()
        self.assertEqual(len(self.mailhost.messages), 1)
        self.assertIn(
            "nohost/plone/passwordreset",
            bytesmessage2string(self.mailhost.messages[-1]),
        )
        mail_password = self._get_view(userid="local_user@example.com")
        with mock.patch(
            "ploneintranet.layout.browser.actions.PasswordHelper.find_profiles",
            return_value=[self.profile],
        ):
            mail_password()
        self.assertEqual(len(self.mailhost.messages), 2)
        self.assertIn(
            "nohost/plone/passwordreset",
            bytesmessage2string(self.mailhost.messages[-1]),
        )

    def test_user_with_external_source(self):
        api.portal.set_registry_record(
            "ploneintranet.userprofile.primary_external_user_source", "source_users"
        )
        self.profile.source = "source_users"

        mail_password = self._get_view(userid="local_user")
        mail_password()
        self.assertEqual(len(self.mailhost.messages), 1)
        message = DummyMessage(self.mailhost.messages[-1])
        self.assertIn(
            "Please contact the administrator",
            message.body,
        )

        mail_password = self._get_view(userid="ldap_user@example.com")
        with mock.patch(
            "ploneintranet.layout.browser.actions.PasswordHelper.find_profiles",
            return_value=[self.profile],
        ):
            mail_password()

        message = DummyMessage(self.mailhost.messages[-1])
        self.assertIn(
            "Please contact the administrator",
            message.body,
        )

    def test_bogus_user(self):
        mail_password = self._get_view(userid="unknown_user")
        with mock.patch(
            "ploneintranet.layout.browser.actions.PasswordHelper.find_profiles",
            return_value=[],
        ):
            mail_password()
        self.assertEqual(len(self.mailhost.messages), 0)

        mail_password = self._get_view(userid="unknown_user@example.com")
        with mock.patch(
            "ploneintranet.layout.browser.actions.PasswordHelper.find_profiles",
            return_value=[],
        ):
            mail_password()

        self.assertEqual(len(self.mailhost.messages), 1)
        message = DummyMessage(self.mailhost.messages[0])
        self.assertIn(
            "If you need an account, please contact",
            message.body,
        )


def bytesmessage2string(bytesmessage):
    return quopri.decodestring(bytesmessage.decode()).decode().replace("\r\n", "")
