# 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 json
import os
import tempfile

import cherrypy
from cherrypy.test import helper
from sqlalchemy import Column, Integer, String

import cherrypy_foundation.plugins.db  # noqa
from cherrypy_foundation.components import StaticMiddleware
from cherrypy_foundation.tests import SeleniumUnitTest
from cherrypy_foundation.url import url_for

from .. import datatables  # noqa
from .. import i18n  # noqa
from .. import jinja2  # noqa

Base = cherrypy.db.base

env = cherrypy.tools.jinja2.create_env(
    package_name=__package__,
)


class DatatableUser(Base):
    __tablename__ = 'datatables_users'
    id = Column(Integer, primary_key=True)
    username = Column(String)

    def __repr__(self):
        return f"User(id={self.id}, username='{self.username}')"


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


@cherrypy.tools.jinja2(on=False, env=env)
class Root:

    static = Static()

    @cherrypy.expose
    @cherrypy.tools.jinja2(template='test_datatables.html')
    def index(self):
        return {}

    @cherrypy.expose
    @cherrypy.tools.json_out()
    @cherrypy.tools.datatables_out()
    def data_json(self, **kwargs):
        return DatatableUser.query


TEST_USERNAMES = [
    'Alice Johnson',
    'Bob Martinez',
    'Carol White',
    'David Chen',
    'Eva Kowalski',
    # HTML injection attempts
    '<b>Frank Bold</b>',
    '<img src=x onerror=alert(1)>',
    # JavaScript injection attempts
    '<script>alert("XSS")</script>',
    '"><script>alert(document.cookie)</script>',
    # SQL injection attempts
    "Robert'); DROP TABLE datatables_users; --",
    # Template injection
    '{{7*7}}',
    # Unicode / special characters
    'Ève Müller-Ñoño',
    "O'Brien & Sons <Ltd>",
]


class DatatablesToolTest(helper.CPWebCase, SeleniumUnitTest):
    interactive = False

    @classmethod
    def setup_class(cls):
        cls.tempdir = tempfile.TemporaryDirectory(prefix='cherrypy-foundation-', suffix='-db-test')
        super().setup_class()

    @classmethod
    def teardown_class(cls):
        cls.tempdir.cleanup()
        super().teardown_class()

    @classmethod
    def setup_server(cls):
        db_uri = os.environ.get('TEST_DATABASE_URI', f"sqlite:///{cls.tempdir.name}/data.db")
        cherrypy.config.update({'db.uri': db_uri, 'db.autobegin': True, 'db.debug': 1})
        cherrypy.tree.mount(Root(), '/')

    def setUp(self):
        cherrypy.db.create_all()
        return super().setUp()

    def tearDown(self):
        cherrypy.db.drop_all()
        return super().tearDown()

    def test_get_data_json_empty(self):
        # Given a query exposed.
        # When querying data
        self.getPage("/data.json")
        # Then the page return without error
        self.assertStatus(200)
        # Then page include our json data.
        data = json.loads(self.body)
        self.assertEqual(data, {'data': [], 'draw': 0, 'recordsFiltered': 0, 'recordsTotal': 0})

    def test_get_data_json(self):
        # Given a database with data.
        for username in TEST_USERNAMES:
            DatatableUser(username=username).add().commit()
        # When querying data
        self.getPage("/data.json")
        # Then the page return without error
        self.assertStatus(200)
        # Then page include our json data.
        data = json.loads(self.body)
        self.assertEqual(
            data,
            {
                'draw': 0,
                'recordsTotal': 13,
                'recordsFiltered': 13,
                'data': [
                    {'id': 1, 'username': 'Alice Johnson'},
                    {'id': 2, 'username': 'Bob Martinez'},
                    {'id': 3, 'username': 'Carol White'},
                    {'id': 4, 'username': 'David Chen'},
                    {'id': 5, 'username': 'Eva Kowalski'},
                    {'id': 6, 'username': '<b>Frank Bold</b>'},
                    {'id': 7, 'username': '<img src=x onerror=alert(1)>'},
                    {'id': 8, 'username': '<script>alert("XSS")</script>'},
                    {'id': 9, 'username': '"><script>alert(document.cookie)</script>'},
                    {'id': 10, 'username': "Robert'); DROP TABLE datatables_users; --"},
                    {'id': 11, 'username': '{{7*7}}'},
                    {'id': 12, 'username': 'Ève Müller-Ñoño'},
                    {'id': 13, 'username': "O'Brien & Sons <Ltd>"},
                ],
            },
        )

    def test_get_page_selenium(self):
        # Given a database with data.
        for username in TEST_USERNAMES:
            DatatableUser(username=username).add().commit()
        self.assertEqual(13, DatatableUser.query.count())
        # Given a page with datatable
        with self.selenium() as driver:
            # When querying the page
            driver.get(url_for('/'))
            # Then page load without error
            self.assertFalse(driver.get_log('browser'))
            # Then table contains our data.
            table = driver.find_element('css selector', 'table.table')
            table.find_element('xpath', "//td[text()='Alice Johnson']")
            # Then html is escaped by default.
            table.find_element('xpath', "//td[text()='<b>Frank Bold</b>']")
