# Part of Odoo. See LICENSE file for full copyright and licensing details.

from pytz import UTC, timezone
from datetime import datetime
from collections import defaultdict
from functools import reduce

from odoo import api, models
from odoo.fields import Domain
from odoo.tools.intervals import Intervals


class ResPartner(models.Model):
    _inherit = 'res.partner'

    def _get_employees_from_attendees(self, everybody=False):
        domain = (
            Domain('company_id', 'in', self.env.companies.ids)
            & Domain('work_contact_id', '!=', False)
        )
        if not everybody:
            domain &= Domain('work_contact_id', 'in', self.ids)
        return dict(self.env['hr.employee'].sudo()._read_group(domain, groupby=['work_contact_id'], aggregates=['id:recordset']))

    def _get_schedule(self, start_period, stop_period, everybody=False, merge=True):
        """
        This method implements the general case where employees might have different resource calendars at different
        times, even though this is not the case with only this module installed.
        This way it will work with these other modules by just overriding
        `_get_calendar_periods`.

        :param datetime start_period: the start of the period
        :param datetime stop_period: the stop of the period
        :param boolean everybody: represents the "everybody" filter on calendar
        :param boolean merge: specifies if calendar's work_intervals needs to be merged
        :return: schedule (merged or not) by partner
        :rtype: defaultdict
        """
        employees_by_partner = self._get_employees_from_attendees(everybody)
        if not employees_by_partner:
            return {}
        interval_by_calendar = defaultdict()
        calendar_periods_by_employee = defaultdict(list)
        resources_by_calendar = defaultdict(lambda: self.env['resource.resource'])

        # Compute employee's calendars's period and order employee by his involved calendars
        employees = sum(employees_by_partner.values(), start=self.env['hr.employee'])
        calendar_periods_by_employee = employees._get_calendar_periods(start_period, stop_period)
        for employee, calendar_periods in calendar_periods_by_employee.items():
            for _start, _stop, calendar in calendar_periods:
                calendar = calendar or self.env.company.resource_calendar_id
                resources_by_calendar[calendar] += employee.resource_id

        # Compute all work intervals per calendar
        for calendar, resources in resources_by_calendar.items():
            work_intervals = calendar._work_intervals_batch(start_period, stop_period, resources=resources, tz=timezone(calendar.tz))
            del work_intervals[False]
            # Merge all employees intervals to avoid to compute it multiples times
            if merge:
                interval_by_calendar[calendar] = reduce(Intervals.__and__, work_intervals.values())
            else:
                interval_by_calendar[calendar] = work_intervals

        # Compute employee's schedule based own his calendar's periods
        schedule_by_employee = defaultdict(list)
        for employee, calendar_periods in calendar_periods_by_employee.items():
            employee_interval = Intervals([])
            for (start, stop, calendar) in calendar_periods:
                calendar = calendar or self.env.company.resource_calendar_id # No calendar if fully flexible
                interval = Intervals([(start, stop, self.env['resource.calendar'])])
                if merge:
                    calendar_interval = interval_by_calendar[calendar]
                else:
                    calendar_interval = interval_by_calendar[calendar][employee.resource_id.id]
                employee_interval = employee_interval | (calendar_interval & interval)
            schedule_by_employee[employee] = employee_interval

        # Compute partner's schedule equals to the union between his employees's schedule
        schedules = defaultdict()
        for partner, employees in employees_by_partner.items():
            partner_schedule = Intervals([])
            for employee in employees:
                if schedule_by_employee[employee]:
                    partner_schedule = partner_schedule | schedule_by_employee[employee]
            schedules[partner] = partner_schedule
        return schedules

    @api.model
    def get_working_hours_for_all_attendees(self, attendee_ids, date_from, date_to, everybody=False):

        start_period = datetime.fromisoformat(date_from).replace(hour=0, minute=0, second=0, tzinfo=UTC)
        stop_period = datetime.fromisoformat(date_to).replace(hour=23, minute=59, second=59, tzinfo=UTC)

        schedule_by_partner = self.env['res.partner'].browse(attendee_ids)._get_schedule(start_period, stop_period, everybody)
        if not schedule_by_partner:
            return []
        return self._interval_to_business_hours(reduce(Intervals.__and__, schedule_by_partner.values()))

    def _interval_to_business_hours(self, working_intervals):
        # This is the format expected by the fullcalendar library to do the overlay
        return [{
            "daysOfWeek": [(interval[0].weekday() + 1) % 7],
            "startTime":  interval[0].astimezone(timezone(self.env.user.tz or 'UTC')).strftime("%H:%M"),
            "endTime": interval[1].astimezone(timezone(self.env.user.tz or 'UTC')).strftime("%H:%M"),
        } for interval in working_intervals] if working_intervals else [{
            # 7 is used a dummy value to gray the full week
            # Returning an empty list would leave the week uncolored
            "daysOfWeek": [7],
            "startTime":  datetime.today().strftime("00:00"),
            "endTime": datetime.today().strftime("00:00"),
        }]
