# Cherrypy-foundation
# Copyright (C) 2026 IKUS Software
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
import importlib
from unittest import skipUnless
from urllib.parse import urlencode

import cherrypy
from cherrypy.test import helper
from parameterized import parameterized
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support.ui import WebDriverWait
from wtforms.fields import BooleanField, PasswordField, SelectMultipleField, StringField, SubmitField
from wtforms.validators import InputRequired, Length

import cherrypy_foundation.tools.jinja2  # noqa
from cherrypy_foundation.components import StaticMiddleware
from cherrypy_foundation.form import CherryForm
from cherrypy_foundation.tests import SeleniumUnitTest
from cherrypy_foundation.tools.i18n import gettext_lazy as _
from cherrypy_foundation.url import url_for
from cherrypy_foundation.widgets import SideBySideMultiSelect, SwitchWidget

HAS_JINJAX = importlib.util.find_spec("jinjax") is not None

env = cherrypy.tools.jinja2.create_env(
    package_name='cherrypy_foundation.tests',
)


class LoginForm(CherryForm):
    login = StringField(
        _('User'),
        validators=[
            InputRequired(),
            Length(max=256),
        ],
        render_kw={
            "placeholder": _('User'),
            "autocorrect": "off",
            "autocapitalize": "none",
            "autocomplete": "off",
            "autofocus": "autofocus",
        },
    )
    password = PasswordField(
        _('Password'),
        validators=[
            InputRequired(),
            Length(max=256),
        ],
        render_kw={"placeholder": _("Password")},
    )
    persistent = BooleanField(
        _('Remember me'),
        widget=SwitchWidget(),
        # All `label-*` are assigned to the label tag.
        render_kw={'container_class': 'col-sm-6', 'label-attr': 'FOO'},
    )
    optional_field = BooleanField(
        _('Optional'),
        render_kw={
            "data-showif-field": "persistent",
            "data-showif-operator": "eq",
            "data-showif-value": "y",
        },
    )
    multi_select = SelectMultipleField(
        _('Connection Option'), widget=SideBySideMultiSelect(), choices=['ldap', 'ssh', 'telnet']
    )
    submit = SubmitField(
        _('Login'),
        # All `container-*` are assigned to the container tag.
        render_kw={"class": "btn-primary float-end", 'container_class': 'col-sm-6', 'container-attr': 'BAR'},
    )


@cherrypy.tools.i18n(on=False)
@cherrypy.tools.sessions(on=False)
class Static:

    components = StaticMiddleware()


@cherrypy.tools.sessions()
@cherrypy.tools.jinja2(on=False, env=env)
@cherrypy.tools.i18n(
    mo_dir=importlib.resources.files('cherrypy_foundation.tests') / 'locales', domain='messages', default='en'
)
class Root:

    static = Static()

    @cherrypy.expose
    def index(self, **kwargs):
        if 'login' not in cherrypy.session:
            raise cherrypy.HTTPRedirect('/login')
        return 'OK'

    @cherrypy.expose
    @cherrypy.tools.jinja2(template='test_form.html')
    def login(self, **kwargs):
        form = LoginForm()
        if form.validate_on_submit():
            # login user with cherrypy.tools.auth
            cherrypy.session['login'] = True
            raise cherrypy.HTTPRedirect('/')
        return {'form': form}


@skipUnless(HAS_JINJAX, reason='Required jinjax')
class FormTest(helper.CPWebCase, SeleniumUnitTest):
    default_lang = None
    interactive = False

    @classmethod
    def setup_server(cls):
        cherrypy.tree.mount(Root(), '/')

    def test_get_form(self):
        # Given a form
        # When querying the page that include this form
        self.getPage("/login")
        self.assertStatus(200)
        # Then each field is render properly.
        # 1. Check title
        self.assertInBody('test-form')
        # 2. Check user field
        self.assertInBody('<label class="form-label" for="login">User</label>')
        self.assertInBody(
            '<input autocapitalize="none" autocomplete="off" autocorrect="off" autofocus="autofocus" class="form-control" id="login" maxlength="256" name="login" placeholder="User" required type="text" value="">'
        )
        # 3. Check password
        self.assertInBody('<label class="form-label" for="password">Password</label>')
        self.assertMatchesBody(
            '<input class="(cf-field-password ?|form-control ?){2}" id="password" maxlength="256" name="password" placeholder="Password" required type="password" value="">'
        )
        # 4 Switch remember me
        self.assertInBody(
            '<input class="form-check-input" id="persistent" name="persistent" type="checkbox" value="y" switch>'
        )
        self.assertInBody('<label attr="FOO" class="form-label" for="persistent">Remember me</label>')
        # 5. check submit button (regex matches because class could have different order with jinjax<=0.57)
        self.assertInBody('<div  attr="BAR"')
        self.assertMatchesBody(
            '<input class="(btn-primary ?|float-end ?|btn ?){3}" id="submit" name="submit" type="submit" value="Login">'
        )

    @parameterized.expand(
        [
            ('myuser', 'mypassword', 0, 303, False),
            ('myuser', '', 0, 200, 'Password: This field is required.'),
            ('', 'mypassword', 0, 200, 'User: This field is required.'),
        ]
    )
    def test_post_form(self, login, password, persistent, expect_status, expect_error):
        # Given a page with a form.
        # When data is sent to the form.
        self.getPage(
            "/login", method='POST', body=urlencode({'login': login, 'password': password, 'persistent': persistent})
        )
        # Then page return a status
        self.assertStatus(expect_status)
        # Then page may return an error
        if expect_error:
            self.assertInBody(expect_error)

    def test_locale_form(self):
        # Given a page with a form.
        # When data is sent to the form with French.
        self.getPage(
            "/login",
            method='POST',
            headers=[('Accept-Language', 'fr')],
            body=urlencode({'login': '', 'password': 'password'}),
        )
        # Then page return a status
        self.assertStatus(200)
        # Then page may return an error in French
        self.assertInBody('lang="fr"')
        self.assertInBody('Utilisateur: Ce champ est requis.')

    def test_toggle_password_visibility_selenium(self):
        # Given a page with a password field.
        with self.selenium() as driver:
            # When querying the page
            driver.get(url_for('login'))
            # Then page load without error
            self.assertFalse(driver.get_log('browser'))
            # When user click on eye icon
            input_password = driver.find_element('css selector', 'input.cf-field-password')
            self.assertEqual("password", input_password.get_attribute('type'))
            actions = ActionChains(driver)
            actions.move_to_element_with_offset(
                input_password, (input_password.size['width'] / 2) - 10, 0
            ).click().perform()
            # Then password field display the text value
            self.assertFalse(driver.get_log('browser'))
            self.assertEqual("text", input_password.get_attribute('type'))

    def test_showif(self):
        # Given a page with a showif field
        with self.selenium() as driver:
            driver.get(url_for('login'))
            self.assertFalse(driver.get_log('browser'))
            form = driver.find_element('css selector', 'form')
            self.assertNotIn('Optional', form.text)
            # When showif condition are met
            persistent = driver.find_element('css selector', '#persistent')
            persistent.click()
            # Then field is displayed.
            WebDriverWait(driver, timeout=5).until(lambda d: 'Optional' in d.find_element('css selector', 'form').text)
            # When shoif condition is not met
            persistent.click()
            # Then field is hidden.
            WebDriverWait(driver, timeout=5).until(
                lambda d: 'Optional' not in d.find_element('css selector', 'form').text
            )

    def test_invalid_locales(self):
        # Creating a form with an invalid locale
        # Then no exception get raised.
        LoginForm(meta={'locales': 'invalid'})


if __name__ == "__main__":
    root = Root()
    cherrypy.quickstart(root)
