# Copyright 2017-19 Tecnativa - David Vidal
# Copyright 2017 Tecnativa - Pedro M. Baeza
# Copyright 2021 Moka Tourisme (https://www.mokatourisme.fr).
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl-3.0).

from datetime import timedelta

from freezegun import freeze_time

from odoo import fields
from odoo.exceptions import ValidationError
from odoo.tests.common import Form
from odoo.tools import mute_logger

from .common import CommonEventSessionCase


class TestEventSession(CommonEventSessionCase):
    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        cls.event = cls.env["event.event"].create(
            {
                "name": "Test event",
                "use_sessions": True,
                "seats_limited": True,
                "seats_max": 5,
            }
        )
        cls.session = cls.env["event.session"].create(
            {
                "date_begin": "2017-05-26 20:00:00",
                "date_end": "2017-05-26 21:00:00",
                "event_id": cls.event.id,
            }
        )

    def test_session_name_get(self):
        # Case 1: Same tz than user
        name = self.session.name_get()[0][1]
        self.assertEqual(name, "Test event, May 26, 2017, 10:00:00 PM")
        # Case 2: Different timezone
        self.event.date_tz = "UTC"
        name = self.session.name_get()[0][1]
        self.assertEqual(name, "Test event, May 26, 2017, 8:00:00 PM (UTC)")

    def test_check_dates(self):
        with self.assertRaisesRegex(
            ValidationError,
            "The closing date cannot be earlier than the beginning date",
        ):
            self.session.date_end = "2017-05-26 19:00:00"

    def test_open_registrations(self):
        domain = self.session.action_open_registrations()["domain"]
        attendees = self.env["event.registration"].search(domain)
        self.assertEqual(attendees, self.session.registration_ids)

    def test_event_registration_event_begin_end_dates(self):
        """Test that the date_begin and date_end are set to the session's"""
        # Case 1: Even with sessions
        registration = self.env["event.registration"].create(
            {
                "name": "Test attendee",
                "event_id": self.event.id,
                "session_id": self.session.id,
            }
        )
        self.assertEqual(registration.event_begin_date, self.session.date_begin)
        self.assertEqual(registration.event_end_date, self.session.date_end)
        # Case 2: Regular events
        event = self.env.ref("event.event_0")
        registration = self.env["event.registration"].create(
            {
                "name": "Test attendee",
                "event_id": event.id,
            }
        )
        self.assertEqual(registration.event_begin_date, event.date_begin)
        self.assertEqual(registration.event_end_date, event.date_end)

    def test_event_session_dates_located(self):
        self.session.date_tz = "Europe/Paris"
        self.assertEqual(self.session.date_begin_located, "May 26, 2017, 10:00:00 PM")
        self.assertEqual(self.session.date_end_located, "May 26, 2017, 11:00:00 PM")
        self.session.date_tz = "US/Pacific"
        self.assertEqual(self.session.date_begin_located, "May 26, 2017, 1:00:00 PM")
        self.assertEqual(self.session.date_end_located, "May 26, 2017, 2:00:00 PM")

    def test_event_event_sync_from_event_type(self):
        """Test that the event.type fields are synced to the event.event"""
        event_type = self.env["event.type"].create(
            {
                "name": "Test event type",
                "use_sessions": True,
            }
        )
        event = self.env["event.event"].create(
            {
                "name": "Test event",
                "event_type_id": event_type.id,
                "date_begin": self.event.date_begin,
                "date_end": self.event.date_end,
            }
        )
        self.assertEqual(event.use_sessions, True)

    def test_event_session_form(self):
        # Test workaround for this Odoo bug: https://github.com/odoo/odoo/pull/91373
        session_form = Form(
            self.env["event.session"].with_context(
                default_event_id=self.event.id,
            )
        )
        self.assertEqual(session_form.event_id, self.event)
        self.assertEqual(session_form.name, self.event.name)

    def test_event_event_use_sessions_switch(self):
        # Case 1: We can't change an event to use_sessions after registrations
        event = self.env["event.event"].create(
            {
                "name": "Test event",
                "date_begin": self.event.date_begin,
                "date_end": self.event.date_end,
            }
        )
        self.env["event.registration"].create(
            {
                "event_id": event.id,
                "name": "Test attendee",
            }
        )
        msg = "You can't enable/disable sessions on events with registrations."
        with self.assertRaisesRegex(ValidationError, msg):
            event.use_sessions = True
        # Case 2: We can change it back, if we have no registrations
        # In fact event.sessions are removed when doing so
        self.event.use_sessions = False
        self.assertFalse(self.session.exists())

    @mute_logger("odoo.models.unlink")
    def test_event_event_sessions_count(self):
        """Test that the sessions count is computed correctly"""
        self.assertEqual(self.event.session_count, 1)
        self.session.unlink()
        self.assertEqual(self.event.session_count, 0)

    def test_event_message_subscribe_organizer(self):
        """Test that the organizer is subscribed to the sessions"""
        organizer = self.env["res.partner"].create({"name": "Test organizer"})
        # Case 1: Updating the event's organizer
        self.event.organizer_id = organizer
        self.assertIn(organizer, self.session.message_partner_ids)
        # Case 2: Creating new sessions
        session = self.env["event.session"].create(
            {
                "date_begin": "2017-05-27 20:00:00",
                "date_end": "2017-05-27 21:00:00",
                "event_id": self.event.id,
            }
        )
        self.assertIn(organizer, session.message_partner_ids)

    def test_session_seats(self):
        """Test event session seats constraints"""
        self.assertEqual(self.event.seats_unconfirmed, self.session.seats_unconfirmed)
        self.assertEqual(self.event.seats_used, self.session.seats_used)
        vals = {
            "name": "Test Attendee",
            "event_id": self.event.id,
            "session_id": self.session.id,
            "state": "open",
        }
        # Fill the event session with attendees
        self.env["event.registration"].create([vals] * self.session.seats_available)
        # Try to create another one
        with self.assertRaisesRegex(
            ValidationError, r"There are not enough seats available for:"
        ), self.cr.savepoint():
            self.env["event.registration"].create(vals)
        # Attempt to create a draft registration on a full session
        with self.assertRaisesRegex(
            ValidationError, "No more seats available for this session."
        ), self.cr.savepoint():
            self.env["event.registration"].create(dict(vals, state="draft"))
        # Temporarily allow to create a draft registration and attempt to confirm it
        self.event.seats_limited = False
        registration = self.env["event.registration"].create(dict(vals, state="draft"))
        self.event.seats_limited = True
        with self.assertRaisesRegex(
            ValidationError, r"There are not enough seats available for:"
        ), self.cr.savepoint():
            registration.action_confirm()
            registration.flush_recordset()

    def test_event_seats(self):
        """Test that event.event seats constraints do not apply to sessions"""
        # Case: Event has a limit of 5 seats, but it should apply per-session
        self.event.seats_max = 5
        self.event.seats_limited = True
        # Fill session with attendees
        vals = {
            "name": "Test Attendee",
            "event_id": self.event.id,
            "session_id": self.session.id,
            "state": "open",
        }
        self.assertFalse(self.session.event_registrations_sold_out)
        self.env["event.registration"].create([vals] * 5)
        self.assertTrue(self.session.event_registrations_sold_out)
        # Create a second session and fill it too
        session2 = self.session.copy({})
        vals["session_id"] = session2.id
        self.env["event.registration"].create([vals] * 5)
        # Now attempt to move one registration to another session
        with self.assertRaisesRegex(
            ValidationError, r"There are not enough seats available for:"
        ), self.cr.savepoint():
            self.session.registration_ids[0].session_id = session2
        # Attempt to decrease the event seats limit below the existing registrations
        with self.assertRaisesRegex(
            ValidationError, r"There are not enough seats available for:"
        ), self.cr.savepoint():
            self.event.seats_max = 2
            self.event.flush_recordset()

    def test_session_seats_count(self):
        session_1, session_2 = self.env["event.session"].create(
            [
                {
                    "event_id": self.event.id,
                    "date_begin": fields.Datetime.now(),
                    "date_end": fields.Datetime.now() + timedelta(hours=1),
                },
                {
                    "event_id": self.event.id,
                    "date_begin": fields.Datetime.now() + timedelta(days=1),
                    "date_end": fields.Datetime.now() + timedelta(days=1, hours=1),
                },
            ]
        )
        attendee_1, attendee_2, attendee_3 = self.env["event.registration"].create(
            [
                {
                    "name": "S1: First Atendee",
                    "event_id": self.event.id,
                    "session_id": session_1.id,
                },
                {
                    "name": "S1: Second Atendee",
                    "event_id": self.event.id,
                    "session_id": session_1.id,
                },
                {
                    "name": "S2: First Atendee",
                    "event_id": self.event.id,
                    "session_id": session_2.id,
                },
            ]
        )
        self.assertEqual(session_1.seats_unconfirmed, 2)
        self.assertEqual(session_1.seats_reserved, 0)
        self.assertEqual(session_1.seats_expected, 2)
        self.assertEqual(session_1.seats_available_unexpected, 3)
        self.assertEqual(session_2.seats_unconfirmed, 1)
        self.assertEqual(session_2.seats_reserved, 0)
        self.assertEqual(session_2.seats_expected, 1)
        self.assertEqual(session_2.seats_available_unexpected, 4)
        self.assertEqual(self.event.seats_unconfirmed, 3)
        self.assertEqual(self.event.seats_reserved, 0)
        self.assertEqual(self.event.seats_expected, 3)
        attendee_1.action_confirm()
        self.assertEqual(session_1.seats_unconfirmed, 1)
        self.assertEqual(session_1.seats_reserved, 1)
        self.assertEqual(session_2.seats_unconfirmed, 1)
        self.assertEqual(session_2.seats_reserved, 0)
        self.assertEqual(self.event.seats_unconfirmed, 2)
        self.assertEqual(self.event.seats_reserved, 1)
        attendee_2.action_confirm()
        self.assertEqual(session_1.seats_unconfirmed, 0)
        self.assertEqual(session_1.seats_reserved, 2)
        self.assertEqual(session_2.seats_unconfirmed, 1)
        self.assertEqual(session_2.seats_reserved, 0)
        self.assertEqual(self.event.seats_unconfirmed, 1)
        self.assertEqual(self.event.seats_reserved, 2)
        attendee_3.action_confirm()
        self.assertEqual(session_1.seats_unconfirmed, 0)
        self.assertEqual(session_1.seats_reserved, 2)
        self.assertEqual(session_2.seats_unconfirmed, 0)
        self.assertEqual(session_2.seats_reserved, 1)
        self.assertEqual(self.event.seats_unconfirmed, 0)
        self.assertEqual(self.event.seats_reserved, 3)

    def test_event_session_is_ongoing(self):
        # Case 1: Session is ongoing
        session = self.env["event.session"].create(
            {
                "event_id": self.event.id,
                "date_begin": fields.Datetime.now() - timedelta(hours=1),
                "date_end": fields.Datetime.now() + timedelta(hours=1),
            }
        )
        ongoing = self.env["event.session"].search([("is_ongoing", "=", True)])
        not_ongoing = self.env["event.session"].search([("is_ongoing", "=", False)])
        self.assertTrue(session.is_ongoing)
        self.assertIn(session, ongoing)
        self.assertNotIn(session, not_ongoing)
        # Case 2: It isn't
        session.write(
            {
                "date_begin": fields.Datetime.now() + timedelta(days=1),
                "date_end": fields.Datetime.now() + timedelta(days=1, hours=1),
            }
        )
        ongoing = self.env["event.session"].search([("is_ongoing", "=", True)])
        not_ongoing = self.env["event.session"].search([("is_ongoing", "=", False)])
        self.assertFalse(session.is_ongoing)
        self.assertIn(session, not_ongoing)
        self.assertNotIn(session, ongoing)

    def test_event_session_is_finished(self):
        # Case 1: Session is finished
        session = self.env["event.session"].create(
            {
                "event_id": self.event.id,
                "date_begin": fields.Datetime.now() - timedelta(hours=2),
                "date_end": fields.Datetime.now() - timedelta(hours=1),
            }
        )
        finished = self.env["event.session"].search([("is_finished", "=", True)])
        not_finished = self.env["event.session"].search([("is_finished", "=", False)])
        self.assertTrue(session.is_finished)
        self.assertIn(session, finished)
        self.assertNotIn(session, not_finished)
        # Case 2: It isn't
        session.write(
            {
                "date_begin": fields.Datetime.now() + timedelta(days=1),
                "date_end": fields.Datetime.now() + timedelta(days=1, hours=1),
            }
        )
        finished = self.env["event.session"].search([("is_finished", "=", True)])
        not_finished = self.env["event.session"].search([("is_finished", "=", False)])
        self.assertFalse(session.is_finished)
        self.assertIn(session, not_finished)
        self.assertNotIn(session, finished)

    def test_event_session_registrations_open(self):
        with freeze_time("2017-05-26 20:30:00"):
            self.session.invalidate_recordset(["event_registrations_open"])
            self.assertTrue(self.session.event_registrations_open)
        with freeze_time("2017-05-30 20:00:00"):
            self.session.invalidate_recordset(["event_registrations_open"])
            self.assertFalse(self.session.event_registrations_open)

    def test_event_session_action_set_done(self):
        self.assertEqual(self.session.stage_id, self.stage_new)
        self.session.action_set_done()
        self.assertEqual(self.session.stage_id, self.stage_done)

    def test_event_session_gc(self):
        self.assertEqual(self.session.stage_id, self.stage_new)
        with freeze_time("2017-05-26 20:30:00"):
            self.env["event.session"]._gc_mark_events_done()
            self.assertEqual(self.session.stage_id, self.stage_new, "Not done yet")
        with freeze_time("2017-05-27 20:00:00"):
            self.env["event.session"]._gc_mark_events_done()
            self.assertEqual(self.session.stage_id, self.stage_done, "Done")

    def test_event_session_update_multi(self):
        """Test the session series update"""
        sessions = self.env["event.session"].create(
            [
                {
                    "event_id": self.event.id,
                    "date_begin": "2017-05-20 20:00:00",
                    "date_end": "2017-05-20 21:00:00",
                },
                {
                    "event_id": self.event.id,
                    "date_begin": "2017-05-21 20:00:00",
                    "date_end": "2017-05-21 21:00:00",
                },
                {
                    "event_id": self.event.id,
                    "date_begin": "2017-05-22 20:00:00",
                    "date_end": "2017-05-22 21:00:00",
                },
                {
                    "event_id": self.event.id,
                    "date_begin": "2017-05-23 20:00:00",
                    "date_end": "2017-05-23 21:00:00",
                },
            ]
        )
        sessions = sessions.with_context(active_test=False)
        session1, session2, session3, session4 = sessions
        # Case 1: Archive session 1
        session1.write({"active": False, "session_update": "this"})
        self.assertFalse(session1.active)
        self.assertTrue(session2.active)
        self.assertTrue(session3.active)
        self.assertTrue(session4.active)
        # Case 2: Archive all
        session2.write({"active": False, "session_update": "all"})
        self.assertFalse(session1.active)
        self.assertFalse(session2.active)
        self.assertFalse(session3.active)
        self.assertFalse(session4.active)
        # Case 3: Unarchive starting from session 3
        session3.write({"active": True, "session_update": "subsequent"})
        self.assertFalse(session1.active)
        self.assertFalse(session2.active)
        self.assertTrue(session3.active)
        self.assertTrue(session4.active)
