# 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 os
import tempfile
from datetime import datetime, timedelta, timezone

import cherrypy
from cherrypy.test import helper
from parameterized import parameterized_class

from cherrypy_foundation.db_sessions import DbSession, SessionModel
from cherrypy_foundation.sessions import session_lock

Base = cherrypy.db.base


class MySessionModel(SessionModel, Base):
    __tablename__ = 'sessions'


@cherrypy.tools.sessions(on=True)
class Root:

    @cherrypy.expose
    def index(self, value='OK'):
        if value:
            with session_lock() as sess:
                sess['value'] = value
        return sess['value']

    @cherrypy.expose
    def logout(self):
        with session_lock() as session:
            session.clear()
            session.regenerate()

    @cherrypy.expose
    def clean_up(self):
        cherrypy.serving.session.clean_up()


@parameterized_class(
    ('autobegin',),
    [
        (True,),
        (False,),
    ],
)
class DbSessionTest(helper.CPWebCase):
    interactive = False
    autobegin = False

    @classmethod
    def setup_class(cls):
        cls.tempdir = tempfile.TemporaryDirectory(prefix='cherrypy-foundation-', suffix='-db-session-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.debug': 1,
                'db.autobegin': cls.autobegin,
                # Configure database
                'db.uri': db_uri,
                # Configure sessions.
                'tools.sessions.locking': 'explicit',
                'tools.sessions.storage_class': DbSession,
                'tools.sessions.model_class': MySessionModel,
            }
        )
        cherrypy.tree.mount(Root(), '/')

    def setUp(self):
        with cherrypy.db.session.begin():
            cherrypy.db.create_all(commit=False)
        return super().setUp()

    def tearDown(self):
        with cherrypy.db.session.begin():
            cherrypy.db.drop_all(commit=False)
        return super().tearDown()

    @property
    def _session_id(self):
        """Return session id from cookie."""
        if hasattr(self, 'cookies') and self.cookies:
            for unused, value in self.cookies:
                for part in value.split(';'):
                    key, unused, value = part.partition('=')
                    if key == 'session_id':
                        return value

    def test_get_page(self):
        # Given a page with session enabled
        # When the page get queried
        self.getPage("/")
        # Then a session is created with a id
        self.assertStatus(200)
        self.assertTrue(self._session_id)
        # Then this session is created on disk.
        with cherrypy.db.session.begin():
            sess = MySessionModel.query.filter(MySessionModel.session_id == self._session_id).one_or_none()
            self.assertIsNotNone(sess)
            # Then session contains our data.
            self.assertEqual({'value': 'OK'}, sess.data)

    def test_regenerate(self):
        # Given a user with a session.
        self.getPage("/")
        first_session_id = self._session_id
        with cherrypy.db.session.begin():
            sess = MySessionModel.query.filter(MySessionModel.session_id == first_session_id).one_or_none()
            self.assertIsNotNone(sess)
            self.assertEqual({'value': 'OK'}, sess.data)
        # When session get regenerate
        self.getPage("/logout", self.cookies)
        # Then previous session got deleted
        with cherrypy.db.session.begin():
            count = MySessionModel.query.filter(MySessionModel.session_id == first_session_id).count()
            self.assertEqual(0, count)
        # Then a new session was created
        with cherrypy.db.session.begin():
            sess = MySessionModel.query.filter(MySessionModel.session_id == self._session_id).one_or_none()
            self.assertIsNotNone(sess)

    def test_clean_up_session(self):
        # Given a server with a session
        self.getPage('/')
        self.assertStatus(200)
        with cherrypy.db.session.begin():
            self.assertEqual(1, len(MySessionModel.query.all()))
        # When this session get old
        with cherrypy.db.session.begin():
            session = MySessionModel.query.filter(MySessionModel.session_id == self._session_id).one()
            session.expiration_time = datetime.now(tz=timezone.utc) - timedelta(seconds=1)
            session.add()
        # Then the session get deleted by clean_up process
        self.getPage('/clean-up')
        # Then session is deleted
        with cherrypy.db.session.begin():
            self.assertEqual(0, MySessionModel.query.filter(MySessionModel.session_id == self._session_id).count())


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