# copyright 2003-2023 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact https://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
#
# CubicWeb is free software: you can redistribute it and/or modify it under the
# terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 2.1 of the License, or (at your option)
# any later version.
#
# CubicWeb is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb.  If not, see <https://www.gnu.org/licenses/>.

import time
from datetime import datetime

import pytz
from lxml import html

from cubicweb import Binary, ValidationError
from cubicweb.devtools import BASE_URL

from cubicweb_web.devtools.testlib import WebCWTC
from cubicweb_web.formfields import (
    IntField,
    StringField,
    RichTextField,
    PasswordField,
    DateTimeField,
    FileField,
    EditableFileField,
    TZDatetimeField,
)
from cubicweb_web.formwidgets import DateTimePicker, JQueryDateTimePicker
from cubicweb_web.views.formrenderers import FormRenderer
from cubicweb_web.views.forms import EntityFieldsForm, FieldsForm
from cubicweb_web.views.workflow import ChangeStateForm


class FieldsFormCWTC(WebCWTC):
    def test_form_field_format(self):
        with self.admin_access.web_request() as req:
            form = FieldsForm(req, None)
            self.assertEqual(StringField().format(form), "text/plain")
            req.cnx.execute(
                'INSERT CWProperty X: X pkey "ui.default-text-format", X value "text/rest", X for_user U WHERE U login "admin"'
            )
            req.cnx.commit()
        with self.admin_access.web_request() as req:
            form = FieldsForm(req, None)
            self.assertEqual(StringField().format(form), "text/rest")

    def test_process_posted(self):
        class AForm(FieldsForm):
            anint = IntField()
            astring = StringField()

        with self.admin_access.web_request(
            anint="1", astring="2", _cw_fields="anint,astring"
        ) as req:
            form = AForm(req)
            self.assertEqual(form.process_posted(), {"anint": 1, "astring": "2"})
        with self.admin_access.web_request(
            anint="1a", astring="2b", _cw_fields="anint,astring"
        ) as req:
            form = AForm(req)
            self.assertRaises(ValidationError, form.process_posted)

    def test_jqdt_process_data(self):
        widget = JQueryDateTimePicker()
        field = DateTimeField("jqdt")
        with self.admin_access.web_request(
            **{"jqdt-date": "", "jqdt-time": "00:00"}
        ) as req:
            self._cw = req
            self.formvalues = {}
            date = widget.process_field_data(self, field)
            self.assertIsNone(date)


class EntityFieldsFormCWTC(WebCWTC):
    def test_form_field_choices(self):
        with self.admin_access.web_request() as req:
            b = req.create_entity(
                "BlogEntry", title="di mascii code", content="a best-seller"
            )
            t = req.create_entity("Tag", name="x")
            form1 = self.vreg["forms"].select("edition", req, entity=t)
            choices = [
                reid
                for rview, reid in form1.field_by_name(
                    "tags", "subject", t.e_schema
                ).choices(form1)
            ]
            self.assertIn(str(b.eid), choices)
            form2 = self.vreg["forms"].select("edition", req, entity=b)
            choices = [
                reid
                for rview, reid in form2.field_by_name(
                    "tags", "object", t.e_schema
                ).choices(form2)
            ]
            self.assertIn(str(t.eid), choices)

            b.cw_clear_all_caches()
            t.cw_clear_all_caches()
            req.cnx.execute("SET X tags Y WHERE X is Tag, Y is BlogEntry")

            choices = [
                reid
                for rview, reid in form1.field_by_name(
                    "tags", "subject", t.e_schema
                ).choices(form1)
            ]
            self.assertIn(str(b.eid), choices)
            choices = [
                reid
                for rview, reid in form2.field_by_name(
                    "tags", "object", t.e_schema
                ).choices(form2)
            ]
            self.assertIn(str(t.eid), choices)

    def test_form_field_choices_new_entity(self):
        with self.admin_access.web_request() as req:
            e = self.vreg["etypes"].etype_class("CWUser")(req)
            form = self.vreg["forms"].select("edition", req, entity=e)
            unrelated = [
                rview
                for rview, reid in form.field_by_name("in_group", "subject").choices(
                    form
                )
            ]
            # should be default groups but owners, i.e. managers, users, guests
            self.assertEqual(unrelated, ["guests", "managers", "users"])

    def test_consider_req_form_params(self):
        with self.admin_access.web_request() as req:
            e = self.vreg["etypes"].etype_class("CWUser")(req)
            e.eid = "A"
            with self.admin_access.web_request(login="toto") as toto_req:
                form = EntityFieldsForm(toto_req, None, entity=e)
                field = StringField(name="login", role="subject", eidparam=True)
                form.append_field(field)
                form.build_context({})
                self.assertEqual(field.widget.values(form, field), ("toto",))

    def test_linkto_field_duplication_inout(self):
        with self.admin_access.web_request() as req:
            e = self.vreg["etypes"].etype_class("CWUser")(req)
            e.eid = "A"
            e._cw = req
            geid = req.cnx.execute('CWGroup X WHERE X name "users"')[0][0]
            req.form["__linkto"] = "in_group:%s:subject" % geid
            form = self.vreg["forms"].select("edition", req, entity=e)
            form.content_type = "text/html"
            data = []
            form.render(w=data.append)
            pageinfo = self._check_html("\n".join(data), form, template=None)
            pageinfo.find_tag("select", False)
            ok = False
            for selectnode in pageinfo.matching_nodes(
                "select", name="from_in_group-subject:A"
            ):
                for optionnode in selectnode:
                    self.assertEqual(optionnode.get("value"), str(geid))
                    self.assertEqual(ok, False)
                    ok = True
            pageinfo.find_tag("input", False)
            self.assertFalse(list(pageinfo.matching_nodes("input", name="__linkto")))

    def test_reledit_composite_field(self):
        with self.admin_access.web_request() as req:
            rset = req.execute(
                'INSERT BlogEntry X: X title "cubicweb.org", X content "hop"'
            )
            form = self.vreg["views"].select(
                "reledit", req, rset=rset, row=0, rtype="content"
            )
            data = form.render(
                row=0, rtype="content", formid="base", action="edit_rtype"
            )
            self.assertIn("content_format", data)

    def test_form_generation_time(self):
        with self.admin_access.web_request() as req:
            e = req.create_entity("BlogEntry", title="cubicweb.org", content="hop")
            expected_field_name = "__form_generation_time:%d" % e.eid

            ts_before = time.time()
            form = self.vreg["forms"].select("edition", req, entity=e)
            ts_after = time.time()

            data = []
            form.render(action="edit", w=data.append)
            html_form = html.fromstring("".join(data)).forms[0]
            fields = dict(html_form.form_values())
            self.assertIn(expected_field_name, fields)
            ts = float(fields[expected_field_name])
            self.assertTrue(ts_before < ts < ts_after)

    # form tests ##############################################################

    def test_form_inheritance(self):
        with self.admin_access.web_request() as req:

            class CustomChangeStateForm(ChangeStateForm):
                hello = IntField(name="youlou")
                creation_date = DateTimeField(widget=DateTimePicker)

            form = CustomChangeStateForm(
                req, redirect_path="perdu.com", entity=req.user
            )
            data = []
            form.render(
                w=data.append,
                formvalues=dict(state=123, trcomment="", trcomment_format="text/plain"),
            )

    def test_change_state_form(self):
        with self.admin_access.web_request() as req:
            form = ChangeStateForm(req, redirect_path="perdu.com", entity=req.user)
            data = []
            form.render(
                w=data.append,
                formvalues=dict(state=123, trcomment="", trcomment_format="text/plain"),
            )

    # fields tests ############################################################

    def _render_entity_field(self, req, name, form):
        form.build_context({})
        renderer = FormRenderer(req)
        return form.field_by_name(name, "subject").render(form, renderer)

    def _test_richtextfield(self, req, expected):
        class RTFForm(EntityFieldsForm):
            description = RichTextField(eidparam=True, role="subject")

        state = self.vreg["etypes"].etype_class("State")(req)
        state.eid = "S"
        form = RTFForm(req, redirect_path="perdu.com", entity=state)
        # make it think it can use fck editor anyway
        form.field_by_name(
            "description", "subject"
        ).format = lambda form, field=None: "text/html"
        self.assertMultiLineEqual(
            self._render_entity_field(req, "description", form),
            expected % {"eid": state.eid},
        )

    def test_richtextfield_1(self):
        with self.admin_access.web_request() as req:
            req.use_fckeditor = lambda: False
            self._test_richtextfield(
                req,
                """<select id="description_format-subject:%(eid)s" name="description_format-subject:%(eid)s" size="1" style="display: block">
"""
                + """<option selected="selected" value="text/html">text/html</option>
<option value="text/markdown">text/markdown</option>
<option value="text/plain">text/plain</option>
<option value="text/rest">text/rest</option>
</select><textarea cols="80" id="description-subject:%(eid)s" name="description-subject:%(eid)s" onkeyup="autogrow(this)" rows="2"></textarea>""",
            )

    def test_richtextfield_2(self):
        with self.admin_access.web_request() as req:
            req.use_fckeditor = lambda: True
            self._test_richtextfield(
                req,
                '<input name="description_format-subject:%(eid)s" type="hidden" value="text/html" /><textarea cols="80" cubicweb:type="wysiwyg" id="description-subject:%(eid)s" name="description-subject:%(eid)s" onkeyup="autogrow(this)" rows="2"></textarea>',
            )

    def test_filefield(self):
        class FFForm(EntityFieldsForm):
            data = FileField(
                format_field=StringField(
                    name="data_format", max_length=50, eidparam=True, role="subject"
                ),
                encoding_field=StringField(
                    name="data_encoding", max_length=20, eidparam=True, role="subject"
                ),
                eidparam=True,
                role="subject",
            )

        with self.admin_access.web_request() as req:
            file = req.create_entity(
                "File",
                data_name="pouet.txt",
                data_encoding="UTF-8",
                data=Binary(b"new widgets system"),
            )
            form = FFForm(req, redirect_path="perdu.com", entity=file)
            self.assertMultiLineEqual(
                self._render_entity_field(req, "data", form),
                f"""<input id="data-subject:%(eid)s" name="data-subject:%(eid)s" type="file" value="" />
<a href="javascript: toggleVisibility(&#39;data-subject:%(eid)s-advanced&#39;)" title="show advanced fields"><img src="{BASE_URL}data/puce_down.png" alt="show advanced fields"/></a>
<div id="data-subject:%(eid)s-advanced" class="hidden">
<label for="data_format-subject:%(eid)s">data_format</label><input id="data_format-subject:%(eid)s" maxlength="50" name="data_format-subject:%(eid)s" size="45" type="text" value="text/plain" /><br/>
<label for="data_encoding-subject:%(eid)s">data_encoding</label><input id="data_encoding-subject:%(eid)s" maxlength="20" name="data_encoding-subject:%(eid)s" size="20" type="text" value="UTF-8" /><br/>
</div>
<br/>
<input name="data-subject__detach:%(eid)s" type="checkbox" />
detach attached file"""
                % {"eid": file.eid},
            )

    def test_editablefilefield(self):
        class EFFForm(EntityFieldsForm):
            data = EditableFileField(
                format_field=StringField(
                    "data_format", max_length=50, eidparam=True, role="subject"
                ),
                encoding_field=StringField(
                    "data_encoding", max_length=20, eidparam=True, role="subject"
                ),
                eidparam=True,
                role="subject",
            )

        with self.admin_access.web_request() as req:
            file = req.create_entity(
                "File",
                data_name="pouet.txt",
                data_encoding="UTF-8",
                data=Binary(b"new widgets system"),
            )
            form = EFFForm(req, redirect_path="perdu.com", entity=file)
            self.assertMultiLineEqual(
                self._render_entity_field(req, "data", form),
                f"""<input id="data-subject:%(eid)s" name="data-subject:%(eid)s" type="file" value="" />
<a href="javascript: toggleVisibility(&#39;data-subject:%(eid)s-advanced&#39;)" title="show advanced fields"><img src="{BASE_URL}data/puce_down.png" alt="show advanced fields"/></a>
<div id="data-subject:%(eid)s-advanced" class="hidden">
<label for="data_format-subject:%(eid)s">data_format</label><input id="data_format-subject:%(eid)s" maxlength="50" name="data_format-subject:%(eid)s" size="45" type="text" value="text/plain" /><br/>
<label for="data_encoding-subject:%(eid)s">data_encoding</label><input id="data_encoding-subject:%(eid)s" maxlength="20" name="data_encoding-subject:%(eid)s" size="20" type="text" value="UTF-8" /><br/>
</div>
<br/>
<input name="data-subject__detach:%(eid)s" type="checkbox" />
detach attached file
<p><b>You can either submit a new file using the browse button above, or choose to remove already uploaded file by checking the "detach attached file" check-box, or edit file content online with the widget below.</b></p>
<textarea cols="80" name="data-subject:%(eid)s" onkeyup="autogrow(this)" rows="3">new widgets system</textarea>"""
                % {"eid": file.eid},
            )

    def _modified_tzdatenaiss(self, eid, date_and_time_str=None):
        ctx = {}
        if date_and_time_str:
            datestr, timestr = date_and_time_str
            ctx["tzdatenaiss-subjectdate:%d" % eid] = datestr
            ctx["tzdatenaiss-subjecttime:%d" % eid] = timestr
        with self.admin_access.web_request(**ctx) as req:
            form = EntityFieldsForm(req, None, entity=req.entity_from_eid(eid))
            field = TZDatetimeField(name="tzdatenaiss", eidparam=True, role="subject")
            form.append_field(field)
            form.build_context({})
            return field.has_been_modified(form)

    def test_tzdatetimefield(self):
        """Comparison of the tz-aware database-stored value and the posted data
        should not crash, and the posted data should be considered UTC"""
        tzd = datetime.now(pytz.utc).replace(second=0, microsecond=0)
        datestr, timestr = tzd.strftime("%Y/%m/%d %H:%M").split()
        with self.admin_access.web_request() as req:
            eid = req.create_entity("Personne", nom="Flo", tzdatenaiss=tzd).eid
            req.cnx.commit()

        modified = self._modified_tzdatenaiss(eid, (datestr, timestr))
        self.assertFalse(modified)

        modified = self._modified_tzdatenaiss(eid, ("2016/05/04", "15:07"))
        self.assertTrue(modified)

        modified = self._modified_tzdatenaiss(eid, None)
        self.assertTrue(modified)

    def test_passwordfield(self):
        class PFForm(EntityFieldsForm):
            upassword = PasswordField(eidparam=True, role="subject")

        with self.admin_access.web_request() as req:
            form = PFForm(req, redirect_path="perdu.com", entity=req.user)
            self.assertMultiLineEqual(
                self._render_entity_field(req, "upassword", form),
                """<input id="upassword-subject:%(eid)s" name="upassword-subject:%(eid)s" type="password" value="" />
<br/>
<input name="upassword-subject-confirm:%(eid)s" type="password" value="" />
&#160;
<span class="emphasis">confirm password</span>"""
                % {"eid": req.user.eid},
            )

    # def test_datefield(self):
    #     class DFForm(EntityFieldsForm):
    #         creation_date = DateTimeField(widget=Input)
    #     form = DFForm(self.req, entity=self.entity)
    #     init, cur = (fromstring(self._render_entity_field(attr, form)).get('value')
    #                  for attr in ('edits-creation_date', 'creation_date'))
    #     self.assertEqual(init, cur)


if __name__ == "__main__":
    import unittest

    unittest.main()
