# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import datetime, UTC
from zoneinfo import ZoneInfo

from odoo.tests import Form
from odoo.tests.common import tagged, TransactionCase


@tagged('at_install', '-post_install')  # LEGACY at_install
class TestResourceCalendar(TransactionCase):

    def test_fully_flexible_attendance_interval_duration(self):
        """
        Test that the duration of a fully flexible attendance interval is correctly computed.
        """
        calendar = self.env['resource.calendar'].create({
            'name': 'Standard Calendar',
        })
        resource = self.env['resource.resource'].create({
            'name': 'Wade Wilson',
            'calendar_id': False,  # Fully-flexible because no calendar is set
            'tz': 'America/New_York',  # -04:00 UTC offset in the summer
        })
        self.env['resource.calendar.attendance'].create({
            'calendar_id': calendar.id,
            'dayofweek': '2',  # Wednesday
            'hour_from': 14,   # 18:00 UTC
            'hour_to': 17,     # 21:00 UTC
        })
        start_dt = datetime(2025, 6, 4, 18, 0, 0).astimezone(UTC)
        end_dt = datetime(2025, 6, 4, 21, 0, 0).astimezone(UTC)
        resources_per_tz = {
            ZoneInfo('America/New_York'): resource
        }
        result_per_resource_id = calendar._attendance_intervals_batch(
            start_dt, end_dt, resources_per_tz
        )
        start, end, attendance = result_per_resource_id[resource.id]._items[0]
        # For a flexible resource, we expect the output times to match the
        # input times exactly, since the resource has no fixed calendar.
        # Further, the dummy attendance that is created should have a duration
        # equal to the difference between the start and end times.
        self.assertEqual(start, start_dt, "Output start time should match the input start time")
        self.assertEqual(end, end_dt, "Output end time should match the input end time")
        self.assertEqual(attendance.duration_hours, 3.0, "Attendance duration should be 3 hours")

    def test_flexible_calendar_attendance_interval_duration(self):
        """
        Test that the duration of an attendance interval for flexible calendar is correctly computed.
        """
        flex_resource = self.env['resource.resource'].create({
            'name': 'Test FlexResource',
            'calendar_id': False,
            'hours_per_week': 30.0,
            'hours_per_day': 7.0,
            'tz': 'UTC',
        })

        # Case 1: get attendances for the full week.
        # Expected: 7-7-7-7-2 (30 hours total)
        expected_hours = [7, 7, 7, 7, 2]

        start_dt = datetime(2025, 6, 2, 0, 0, 0).astimezone(UTC)
        end_dt = datetime(2025, 6, 7, 23, 59, 59).astimezone(UTC)
        resources_per_tz = {
            UTC: flex_resource
        }
        result_per_resource_id = self.env['resource.calendar']._attendance_intervals_batch(
            start_dt, end_dt, resources_per_tz=resources_per_tz
        )
        self.assertEqual(expected_hours, [(end - start).total_seconds() / 3600 for start, end, dummy_attendance in result_per_resource_id[flex_resource.id]._items])
        self.assertEqual(expected_hours, [dummy_attendance.duration_hours for start, end, dummy_attendance in result_per_resource_id[flex_resource.id]._items])

        # Case 2: check attendances are all contained between start_dt and end_dt
        start_dt = datetime(2025, 6, 2, 11, 0, 0).astimezone(UTC)
        end_dt = datetime(2025, 6, 7, 13, 0, 0).astimezone(UTC)
        result_per_resource_id = self.env['resource.calendar']._attendance_intervals_batch(
            start_dt, end_dt, resources_per_tz=resources_per_tz
        )

        self.assertTrue(start_dt <= result_per_resource_id[flex_resource.id]._items[0][0], "First attendance interval should not start before start_dt")
        self.assertTrue(end_dt >= result_per_resource_id[flex_resource.id]._items[4][1], "Last attendance interval should not end after end_dt")

    def test_public_holiday_calendar_no_company(self):
        self.env['resource.calendar.leaves'].create([{
            'name': "Public Holiday for company",
            'company_id': self.env.company.id,
            'date_from': datetime(2019, 5, 29, 0, 0, 0),
            'date_to': datetime(2019, 5, 30, 0, 0, 0),
            'resource_id': False,
            'count_as': "absence",
        }])
        calendar = self.env['resource.calendar'].create({
            'name': '40 hours/week',
            'hours_per_day': 8,
            'full_time_required_hours': 40,
            'attendance_ids': [(5, 0, 0),
                (0, 0, {'dayofweek': '0', 'duration_hours': 8, 'hour_from': 0, 'hour_to': 0}),
                (0, 0, {'dayofweek': '1', 'duration_hours': 8, 'hour_from': 0, 'hour_to': 0}),
                (0, 0, {'dayofweek': '2', 'duration_hours': 8, 'hour_from': 0, 'hour_to': 0}),
                (0, 0, {'dayofweek': '3', 'duration_hours': 8, 'hour_from': 0, 'hour_to': 0}),
                (0, 0, {'dayofweek': '4', 'duration_hours': 8, 'hour_from': 0, 'hour_to': 0}),
            ],
            'company_id': False,
        })
        self.assertFalse(calendar.company_id)
        date_from = datetime(2019, 5, 27, 0, 0, 0).astimezone(UTC)
        date_to = datetime(2019, 5, 31, 23, 59, 59).astimezone(UTC)
        days = calendar._get_unusual_days(date_from, date_to, self.env.company)
        expected_res = {
            '2019-05-27': False,
            '2019-05-28': False,
            '2019-05-29': True,
            '2019-05-30': False,
            '2019-05-31': False,
        }
        self.assertEqual(days, expected_res)

    def test_resource_calendar_form_view(self):
        calendar = self.env['resource.calendar'].create({
            'name': 'Test calendar',
            'attendance_ids': [(5, 0, 0),
                (0, 0, {'dayofweek': '0', 'hour_from': 8, 'hour_to': 16, 'day_period': 'full_day'}),
                (0, 0, {'dayofweek': '1', 'hour_from': 8, 'hour_to': 16, 'day_period': 'full_day'}),
                (0, 0, {'dayofweek': '2', 'hour_from': 8, 'hour_to': 16, 'day_period': 'full_day'}),
            ],
        })
        calendar_form = Form(calendar)

        self.assertEqual(calendar_form.hours_per_day, 8)
        self.assertEqual(calendar_form.hours_per_week, 24)

        with calendar_form.attendance_ids.edit(0) as line_form:
            line_form.hour_from = 11
        line_form.save()

        # The calendar form values should be recomputed
        self.assertEqual(calendar_form.hours_per_day, 7)
        self.assertEqual(calendar_form.hours_per_week, 21)

        calendar_form.save()
        self.assertEqual(calendar.hours_per_day, 7)
        self.assertEqual(calendar.hours_per_week, 21)

    def test_resource_calendar_form_view_duration_based(self):
        calendar = self.env['resource.calendar'].create({
            'name': 'Test calendar',
            'attendance_ids': [(5, 0, 0),
                (0, 0, {'dayofweek': '0', 'hour_from': 8, 'hour_to': 16, 'day_period': 'full_day', 'duration_based': True}),
                (0, 0, {'dayofweek': '1', 'hour_from': 8, 'hour_to': 16, 'day_period': 'full_day', 'duration_based': True}),
                (0, 0, {'dayofweek': '2', 'hour_from': 8, 'hour_to': 16, 'day_period': 'full_day', 'duration_based': True}),
            ],
        })
        calendar_form = Form(calendar)

        self.assertEqual(calendar_form.hours_per_day, 8)
        self.assertEqual(calendar_form.hours_per_week, 24)

        with calendar_form.attendance_ids.edit(0) as line_form:
            line_form.duration_hours = 5
        line_form.save()

        # The calendar form values should be recomputed
        self.assertEqual(calendar_form.hours_per_day, 7)
        self.assertEqual(calendar_form.hours_per_week, 21)

        calendar_form.save()
        self.assertEqual(calendar.hours_per_day, 7)
        self.assertEqual(calendar.hours_per_week, 21)

    def test_duration_based_average_hours(self):
        """Checks that the average hours for days and weeks are correctly computed when the option Define Amount of
        Hours per Day has been checked. In 19.2, for an attendance to be duration based, we simply create an attendance
        without hour_from and hour_to."""
        calendar = self.env['resource.calendar'].create({
            'name': 'Duration based Calendar',
            'attendance_ids': False,
        })
        with Form(calendar) as form:
            with form.attendance_ids.new() as monday_attendance:
                monday_attendance.dayofweek = '0'
                monday_attendance.duration_hours = 4.0

            with form.attendance_ids.new() as tuesday_attendance:
                tuesday_attendance.dayofweek = '1'
                tuesday_attendance.duration_hours = 4.0

            with form.attendance_ids.new() as wednesday_attendance:
                wednesday_attendance.dayofweek = '2'
                wednesday_attendance.duration_hours = 4.0

            self.assertEqual(
                (form.hours_per_week, form.hours_per_day),
                (12, 4),
            )

    def test_create_WS_when_company_two_weeks_calendar(self):
        self.env.company.resource_calendar_id = False

        calendar_two_week = self.env['resource.calendar'].create({
            'name': 'Company Calendar',
        })

        calendar_two_week.company_id.resource_calendar_id = calendar_two_week
        calendar_two_week.switch_calendar_type()

        with Form(self.env['resource.calendar']) as calendar:
            calendar.save()
            self.assertEqual(len(calendar.attendance_ids_1st_week), 5)
            self.assertEqual(len(calendar.attendance_ids_2nd_week), 5)
            self.assertTrue(calendar.two_weeks_calendar)
