# -*- coding: utf-8 -*-
from __future__ import division

from datetime import datetime, timedelta

from odoo import models, fields, _, exceptions,api
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT as DATE_FORMAT


class HrAttendanceReport(models.Model):
    _name = 'hr.attendance.report'
    _rec_name = 'date_from'
    _order = 'write_date desc'
    _inherit = ['mail.thread', 'mail.activity.mixin']

    date_from = fields.Date()
    date_to = fields.Date()
    line_ids = fields.One2many('hr.attendance.report.line', 'line_id')
    state = fields.Selection(
        [('draft', _('Draft')), ('generated', _('Generated')),
         # ('reviewed', _('Reviewed')),
         ('confirmed', _('HR Manager Approval')),
         ('approved', _('Approved')), ('refused', _('Refused'))], default="draft")
    calendar_ids = fields.Many2many('resource.calendar', string='Calendars')
    name = fields.Char(string=_("Name"), required=True)

    deduct_date_from = fields.Date()
    deduct_date_to = fields.Date()
    company_id = fields.Many2one('res.company', string="Company", default=lambda self: self.env.user.company_id)
    department_ids = fields.Many2many('hr.department', string='Departments')

    selected_employee_ids = fields.Many2many(
        'hr.employee',
        string=_("Employees in Report"),
        domain="[('id', 'in', available_employee_ids)]"  # تحديد النطاق
    )

    available_employee_ids = fields.Many2many(
        'hr.employee',
        compute='_compute_available_employees',
        store=False
    )

    send_email = fields.Boolean(string=" Is Send Email", default=False,store=True)



    @api.onchange('department_ids')
    def _compute_available_employees(self):
        for report in self:
            if report.department_ids:
                employees = self.env['hr.employee'].search([
                    ('department_id', 'in', report.department_ids.ids),
                    ('finger_print', '=', True),
                    ('state', '=', 'open')
                ])
                report.available_employee_ids = employees
            else:
                report.available_employee_ids = self.env['hr.employee'].browse([])

    def unlink(self):
        for rec in self:
            if rec.state != 'draft':
                raise exceptions.UserError(_('You can not delete record in state not in draft'))
        return super(HrAttendanceReport, self).unlink()

    def draft_state(self):
        self.state = "draft"

    def reviewed(self):
        self.state = "reviewed"

    def confirmed(self):
        self.state = "confirmed"

    def set_to_draft(self):
        for line in self.line_ids:
            if line.advantage_id:
                line.advantage_id.draft()
                line.advantage_id.unlink()
        self.line_ids.unlink()
        # self.write({'line_ids': [fields.Command.clear()]})
        self.state = "draft"

    def approved(self):
        for line in self.line_ids:
            employee_contract = line.env['hr.contract'].search([('employee_id', '=', line.employee_name.id),
                                                                ('state', '=', 'program_directory')])
            if employee_contract and line.employee_name.finger_print:
                advantage_arc = line.env['contract.advantage'].create({
                    'benefits_discounts': line.employee_name.resource_calendar_id.deduction_rule.id,
                    'type': 'customize',
                    'date_from': self.deduct_date_from,
                    'date_to': self.deduct_date_to,
                    'amount': line.total_deduction,
                    'employee_id': line.employee_name.id,
                    'contract_advantage_id': line.employee_name.contract_id.id,
                    'out_rule': True,
                    'state': 'confirm',
                    'comments': 'Absence Deduction'})
                line.advantage_id = advantage_arc.id

        self.state = "approved"

    def refused(self):
        for line in self.line_ids:
            line.advantage_id.draft()
            line.advantage_id.unlink()
        self.state = "refused"

    def calcualte_flexible_transaction(self, transactions):
        planed_hours = sum(transactions.filtered(lambda t: t.public_holiday == False).mapped('plan_hours'))
        office_hours = sum(transactions.mapped('office_hours'))
        permission_hours = sum(transactions.filtered(
            lambda t: t.personal_permission_id != False).mapped('total_permission_hours'))
        mission_hours = sum(transactions.filtered(
            lambda t: t.official_id != False
                      and t.official_id.mission_type.duration_type == 'hours').mapped('total_mission_hours'))
        mission_by_days_hours = sum(transactions.filtered(
            lambda t: t.official_id != False
                      and t.official_id.mission_type.duration_type == 'days').mapped('total_mission_hours'))
        absent_hours = sum(transactions.filtered(lambda t: t.is_absent).mapped('plan_hours'))
        actual_absent_days = len(transactions.filtered(lambda t: t.is_absent))
        total_permission = sum(transactions.filtered(lambda t: t.approve_personal_permission).mapped('total_permission_hours'))

        leave_hours = sum(transactions.filtered(lambda t: t.normal_leave == True).mapped('total_leave_hours'))
        working_hours = office_hours + permission_hours + mission_hours + mission_by_days_hours + leave_hours
        missed_hours = planed_hours - working_hours


        missing_punch_transactions = transactions.filtered(
            lambda t: (
                    not t.public_holiday and
                    not t.normal_leave and
                    (
                            (t.sign_in and not t.sign_out) or
                            (not t.sign_in and t.sign_out)
                    )
            )
        )

        missing_punch_hours = sum([
            t.plan_hours - t.office_hours if t.plan_hours > t.office_hours else 0.0
            for t in missing_punch_transactions
        ])

        if missed_hours < 0:
            missed_hours = 0

        early_exit_hours = sum(transactions.filtered(
            lambda t: t.approve_exit_out).mapped('early_exit'))

        lateness_hours = sum(transactions.filtered(
            lambda t: t.approve_lateness).mapped('lateness'))

        break_hours = sum(transactions.filtered(
            lambda t: t.break_duration and t.break_duration > 0).mapped('break_duration'))

        return {'leaves': leave_hours, 'missed_hours': missed_hours, 'mission_by_days': mission_by_days_hours, 'additional_hours': 0.0,'actual_absent_hours': absent_hours,'actual_absent_days':actual_absent_days,'total_permission_hours':total_permission,'missing_punch_hours':missing_punch_hours,'total_lateness':lateness_hours,'total_early_exit':early_exit_hours,'total_break_duration':break_hours}

    def generate_report(self):
        transaction_values = {}
        item_list, mixed_calendar_emps = [], []
        module = self.env['ir.module.module'].sudo()
        official_mission_module = module.search([('state', '=', 'installed'), ('name', '=', 'exp_official_mission')])
        personal_permission_module = module.search([('state', '=', 'installed'), ('name', '=', 'employee_requests')])
        holidays_module = module.search([('state', '=', 'installed'), ('name', '=', 'hr_holidays_public')])
        transaction_pool = self.env['hr.attendance.transaction']
        reason_pool = self.env['hr.reasons.lateness']
        emps = self.selected_employee_ids
        if not emps:
            domain = self._context.get('emp_id') and [('id', '=', self._context['emp_id'])] or []
            domain += [('finger_print', '=', True)]
            if self.department_ids:
                domain += [('department_id', 'in', self.department_ids.ids), ('state', '=', 'open')]
            emps = self.env['hr.employee'].search(domain)
        trans_domain = [('date', '>=', self.date_from), ('date', '<=', self.date_to), ('attending_type', '=', 'in_cal'),('sequence','=',1)]
        if self.calendar_ids: trans_domain += [('calendar_id', 'in', self.calendar_ids.ids)]
        for employee in emps:
            missed_hours, wasted_hours, absent, total_mission, leaves, hours_per_day, count, \
                additional_hours,actual_absent_days,actual_absent_hours,total_permission_hours,missing_punch_hours,lateness_hours,early_exit_hours,break_hours = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ,0 ,0,0,0
            emp_trans_dom = trans_domain.copy() + [('employee_id', '=', employee.id)]
            attendance_transaction = transaction_pool.search(emp_trans_dom)
            if len(attendance_transaction.mapped('calendar_id')) > 1:
                mixed_calendar_emps.append(employee.id)
                continue
            else:
                emp_calendar = attendance_transaction and attendance_transaction[0].calendar_id\
                               or employee.resource_calendar_id
            if emp_calendar.is_flexible:
                flexible_trans = attendance_transaction.filtered(lambda t: t.public_holiday == False)
                no_of_days = len(set(flexible_trans.mapped('date')))
                flexible_days = emp_calendar.number_of_flexi_days
                if no_of_days >= flexible_days:
                    ord_trans_dates = sorted(set(flexible_trans.mapped('date')))
                    index = len(ord_trans_dates) > flexible_days and flexible_days - 1 or len(ord_trans_dates) - 1
                    df = fields.Date.from_string(self.date_from)
                    while no_of_days >= flexible_days:
                        dt = ord_trans_dates[index]
                        current_trans = attendance_transaction.filtered(lambda t: df <= t.date <= dt)
                        hours_dict = self.calcualte_flexible_transaction(current_trans)
                        actual_absent_hours = hours_dict.get('actual_absent_hours', 0.0)
                        actual_absent_days = hours_dict.get('actual_absent_days', 0.0)
                        total_permission_hours =hours_dict.get('total_permission_hours', 0.0)
                        missing_punch_hours = hours_dict.get('missing_punch_hours', 0.0)
                        break_hours =  hours_dict.get('break_hours', 0.0)
                        early_exit_hours = hours_dict.get('early_exit_hours', 0.0)
                        lateness_hours = hours_dict.get('lateness_hours', 0.0)

                        total_mission += hours_dict['mission_by_days']
                        missed_hours += hours_dict['missed_hours']
                        leaves += hours_dict['leaves']
                        additional_hours += hours_dict['additional_hours']
                        df = fields.Date.from_string(dt) + timedelta(1)
                        index = index + flexible_days > len(ord_trans_dates) and len(ord_trans_dates) or index + flexible_days
                        no_of_days -= flexible_days
                    else:
                        if no_of_days and no_of_days < flexible_days:
                            current_trans = attendance_transaction.filtered(lambda t: df <= t.date <= self.date_to)
                            hours_dict = self.calcualte_flexible_transaction(current_trans)
                            actual_absent_hours = hours_dict.get('actual_absent_hours', 0.0)
                            actual_absent_days = hours_dict.get('actual_absent_days', 0.0)
                            break_hours = hours_dict.get('break_hours', 0.0)
                            early_exit_hours = hours_dict.get('early_exit_hours', 0.0)
                            lateness_hours = hours_dict.get('lateness_hours', 0.0)
                            total_permission_hours = hours_dict.get('total_permission_hours', 0.0)
                            missing_punch_hours = hours_dict.get('missing_punch_hours', 0.0)
                            total_mission += hours_dict['mission_by_days']
                            missed_hours += hours_dict['missed_hours']
                            leaves += hours_dict['leaves']
                            additional_hours += hours_dict['additional_hours']
                else:
                    hours_dict = self.calcualte_flexible_transaction(attendance_transaction)
                    missed_hours = hours_dict['missed_hours']
                    actual_absent_hours = hours_dict.get('actual_absent_hours', 0.0)
                    actual_absent_days = hours_dict.get('actual_absent_days', 0.0)
                    break_hours = hours_dict.get('break_hours', 0.0)
                    early_exit_hours = hours_dict.get('early_exit_hours', 0.0)
                    lateness_hours = hours_dict.get('lateness_hours', 0.0)
                    total_permission_hours = hours_dict.get('total_permission_hours', 0.0)
                    leaves, total_mission = hours_dict['leaves'], hours_dict['mission_by_days']
                working_hours_flexible_days = emp_calendar.total_flexible_hours
                values = {
                    'employee_name': employee.id,
                    'delay': 0.0,
                    'leave': leaves,
                    'additional_hours': 0.0,
                    'exist_hours': 0.0,
                    'extra_break_duration': 0.0,
                    'absent': actual_absent_hours + missing_punch_hours + break_hours + early_exit_hours + lateness_hours ,
                    'mission_by_days': total_mission,
                    'absent_days_by_hr': 0.0,
                    'total_hours': missed_hours,
                    'dummy_field': missed_hours,
                    'actual_absent_hours': actual_absent_hours,
                    'actual_absent_days': actual_absent_days,
                    'total_permission_hours':total_permission_hours,
                    'missing_punch_hours':missing_punch_hours ,
                    'total_amount': employee.contract_id.total_allowance,
                    'amount_per_hour': employee.contract_id.total_allowance / working_hours_flexible_days,
                    'total_deduction': ((missed_hours+ missing_punch_hours) * (
                            employee.contract_id.total_allowance / working_hours_flexible_days))
                }
                item_list.append(values)
            elif not emp_calendar.is_flexible:
                emp_trans = attendance_transaction.filtered(lambda t: t.public_holiday == False)
                for attendance in emp_trans:
                    lateness, early_exist, extra_break_duration, hours, additional_hours,missing_punch_hours = 0.0, 0.0, 0.0, 0.0, 0.0, 0.0
                    total_permission, total_mission_by_hour, total_mission_by_day, total_leaves,total_permission_hours = 0.0, 0.0, 0.0, 0.0,0.0
                    total_absent, lateness_hours_by_hr, get_total_amount,actual_absent_hours,actual_absent_days = 0.0, 0.0, 0.0,0.0,0.0
                    transaction_values['id'], transaction_values['name'] = attendance.employee_id.id, attendance.employee_id.name
                    total_hours_for_two_shifts = emp_calendar.shift_one_working_hours + \
                                                 emp_calendar.shift_two_working_hours
                    lateness_reasons = reason_pool.search([('latest_date', '=', attendance.date),
                                                           ('employee_id', '=', employee.id),
                                                           ('state', '=', 'hr_manager')])
                    # if lateness_reasons:
                    #     if attendance.lateness > 0.0 or attendance.early_exit > 0.0:
                    #         for late in lateness_reasons:
                    #             if attendance.date == late.latest_date:
                    #                 lateness_hours_by_hr += (attendance.lateness + attendance.early_exit)
                    if attendance.approve_lateness:
                        lateness += attendance.lateness
                        if lateness_reasons:
                            lateness_hours_by_hr += attendance.lateness
                    if attendance.approve_exit_out:
                        early_exist += attendance.early_exit
                        if lateness_reasons:
                            lateness_hours_by_hr += attendance.early_exit
                    if personal_permission_module and attendance.approve_personal_permission:
                        total_permission += attendance.total_permission_hours
                        total_permission_hours+=attendance.total_permission_hours
                    if official_mission_module and attendance.is_official:
                        if attendance.official_id.mission_type.duration_type == 'days':
                            total_mission_by_day += attendance.total_mission_hours
                        else:
                            total_mission_by_hour += attendance.total_mission_hours
                    if holidays_module and attendance.normal_leave:
                        total_leaves += attendance.total_leave_hours
                    hours += attendance.official_hours
                    extra_break_duration += attendance.break_duration
                    if lateness_reasons:
                        lateness_hours_by_hr += attendance.break_duration
                    additional_hours += attendance.additional_hours

                    if (
                            not attendance.public_holiday and
                            not attendance.normal_leave and
                            (
                                    (attendance.sign_in and not attendance.sign_out) or
                                    (not attendance.sign_in and attendance.sign_out)
                            )
                    ):
                        missing_punch_hours = (
                            attendance.plan_hours - attendance.official_hours
                            if attendance.plan_hours > attendance.official_hours
                            else 0.0
                        )
                        if lateness_reasons:
                            lateness_hours_by_hr += missing_punch_hours

                    if attendance.is_absent:
                        actual_absent_hours +=attendance.plan_hours
                        actual_absent_days +=1
                        total_absent += attendance.plan_hours
                        if attendance.calendar_id.is_full_day:
                            total_absent += attendance.calendar_id.break_duration
                            if lateness_reasons:
                                lateness_hours_by_hr += attendance.plan_hours + attendance.calendar_id.break_duration
                        elif attendance.sequence == 1:
                            total_absent += attendance.calendar_id.shift_one_break_duration
                            if lateness_reasons:
                                lateness_hours_by_hr += attendance.plan_hours + attendance.calendar_id.shift_one_break_duration
                        elif attendance.sequence == 2:
                            total_absent += attendance.calendar_id.shift_two_break_duration
                    else:
                        working_hours = total_permission + total_mission_by_hour + hours + attendance.carried_hours
                        absence_hours = attendance.plan_hours - working_hours
                        total_absent += absence_hours > 0 and absence_hours or 0
                    if total_absent == 0:
                        get_total_hours = (
                                                      lateness + early_exist + extra_break_duration + total_absent) - lateness_hours_by_hr
                    else:
                        get_total_hours = (total_absent - lateness_hours_by_hr)
                    if get_total_hours < 0:
                        get_total_hours = 0
                    if attendance.employee_id.contract_id.state == 'program_directory':
                        get_total_amount = attendance.employee_id.contract_id.total_allowance
                    if emp_calendar.is_full_day:
                        working_hours_per_week = emp_calendar.working_hours * emp_calendar.working_days
                        if working_hours_per_week != 0:
                            get_amount_per_hour = get_total_amount / working_hours_per_week
                        else:
                            get_amount_per_hour = 0
                    else:
                        get_amount_per_hour = get_total_amount / (
                                    total_hours_for_two_shifts * emp_calendar.working_days)
                    values = {
                        'employee_name': attendance.employee_id.id,
                        'delay': lateness,
                        'leave': total_leaves,
                        'exist_hours': early_exist,
                        'extra_break_duration': extra_break_duration,
                        'absent': actual_absent_hours + missing_punch_hours + lateness + early_exist + extra_break_duration,
                        'mission_by_days': total_mission_by_day,
                        'absent_days_by_hr': lateness_hours_by_hr,
                        'total_hours': get_total_hours,
                        'dummy_field': get_total_hours,
                        'total_amount': get_total_amount,
                        'amount_per_hour': get_amount_per_hour,
                        'total_deduction': (get_total_hours)* get_amount_per_hour,
                        'additional_hours': additional_hours,
                        'actual_absent_hours':actual_absent_hours,
                        'total_permission_hours':total_permission_hours,
                        'missing_punch_hours':missing_punch_hours,
                        'actual_absent_days': actual_absent_days,
                    }
                    item_list.append(values)
        from itertools import groupby, tee
        from operator import itemgetter
        grouper = itemgetter("employee_name")
        result = []
        for key, grp in groupby(sorted(item_list, key=grouper), grouper):
            if not isinstance(key, tuple):
                key = [key]
            temp_dict = dict(zip(["employee_name"], key))
            grp1, grp2, grp3, grp4, grp5, grp6, grp7, grp8, grp9, grp10, grp11, grp12, grp13, grp14, grp15,grp16,grp17,grp18,grp19 = tee(grp, 19)
            temp_dict["actual_absent_hours"] = sum(item.get("actual_absent_hours", 0.0) for item in grp16)
            temp_dict["actual_absent_days"] = sum(item.get("actual_absent_days", 0.0) for item in grp19)
            temp_dict["total_permission_hours"] = sum(item.get("total_permission_hours", 0.0) for item in grp17)
            temp_dict["missing_punch_hours"] = sum(item.get("missing_punch_hours", 0.0) for item in grp18)

            temp_dict["delay"] = sum(item["delay"] for item in grp1)
            temp_dict["leave"] = sum(item1["leave"] for item1 in grp2)
            temp_dict["mission_by_days"] = sum(item1["mission_by_days"] for item1 in grp14)
            temp_dict["absent"] = sum(item1["absent"] for item1 in grp3)
            temp_dict["exist_hours"] = sum(item1["exist_hours"] for item1 in grp4)
            temp_dict["extra_break_duration"] = sum(item1["extra_break_duration"] for item1 in grp5)
            temp_dict["absent_days_by_hr"] = sum(item1["absent_days_by_hr"] for item1 in grp6)
            temp_dict["total_hours"] = sum(item["total_hours"] for item in grp7) - (
                        temp_dict["leave"] + temp_dict["mission_by_days"])
            temp_dict["dummy_field"] = sum(item["dummy_field"] for item in grp13)
            temp_dict["total_amount"] = sum(item["total_amount"] for item in grp8) / len(list(grp9))
            temp_dict["amount_per_hour"] = sum(item["amount_per_hour"] for item in grp10) / len(list(grp11))

            ############# re chick total_deduction

            temp_dict["total_deduction"] = temp_dict["total_hours"]*temp_dict["amount_per_hour"]
            #temp_dict["total_deduction"] = sum(item["total_deduction"] for item in grp12)
            temp_dict["additional_hours"] = sum(item["additional_hours"] for item in grp15)
            result.append(temp_dict)
        self.write({'line_ids': [(0, 0, val) for val in result]})
        self.state = "generated"
        if mixed_calendar_emps:
            self.manage_mixed_calendar(mixed_calendar_emps)

    def manage_mixed_calendar(self, emp_calendars):
        for emp in emp_calendars:
            trans = self.env['hr.attendance.transaction'].search([('date', '>=', self.date_from),
                                                                  ('date', '<=', self.date_to),
                                                                  ('employee_id', '=', emp)])
            calendar_ids = trans.mapped('calendar_id').ids
            recs = self.env['hr.attendance.report']
            for cal in calendar_ids:
                tdates = list(set(trans.filtered(lambda l: l.calendar_id.id == cal).mapped('date')))
                tdates.sort()
                ldate = fields.Date.from_string(tdates[0]) + timedelta(len(tdates) - 1)
                if ldate == fields.Date.from_string(tdates[-1]):
                    rec = self.create({'date_from': tdates[0], 'date_to': tdates[-1]})
                    rec.with_context(emp_id=emp).generate_report()
                    recs += rec
                else:
                    ranges, date_range, base = [], [], tdates.pop(0)
                    date_range.append(base)
                    bdate = fields.Date.from_string(base) + timedelta(1)
                    for dt in tdates:
                        if bdate == fields.Date.from_string(dt):
                            date_range.append(dt)
                            bdate += timedelta(1)
                            if dt == tdates[-1]:
                                ranges.append(date_range)
                        else:
                            ranges.append(date_range)
                            date_range, bdate = [dt], fields.Date.from_string(dt) + timedelta(1)
                    for range in ranges:
                        rec = self.create({'date_from': range[0], 'date_to': range[-1]})
                        rec.with_context(emp_id=emp).generate_report()
                        recs += rec
            main_rec = recs[0]
            sum_recs = recs.filtered(lambda r: r.id != main_rec.id)
            sum_line = main_rec.line_ids[0]
            sum_line.write({
                'line_id': self.id,
                'delay': sum_line.delay + sum(sum_recs.mapped('line_ids.delay')),
                'leave': sum_line.leave + sum(sum_recs.mapped('line_ids.leave')),
                'absent': sum_line.absent + sum(sum_recs.mapped('line_ids.absent')),
                'exist_hours': sum_line.exists + sum(sum_recs.mapped('line_ids.exist_hours')),
                'total_hours': sum_line.total_hours + sum(sum_recs.mapped('line_ids.total_hours')),
                'dummy_field': sum_line.dummy_field + sum(sum_recs.mapped('line_ids.dummy_field')),
                'total_deduction': sum_line.total_deduction + sum(sum_recs.mapped('line_ids.total_deduction')),
                'absent_days_by_hr': sum_line.absent_days_by_hr + sum(sum_recs.mapped('line_ids.absent_days_by_hr')),
                'mission_by_days': sum_line.mission_by_days + sum(sum_recs.mapped('line_ids.mission_by_days')),
                'extra_break_duration': sum_line.extra_break_duration + sum(
                    sum_recs.mapped('line_ids.extra_break_duration')),
                'additional_hours': sum_line.additional_hours + sum(sum_recs.mapped('line_ids.additional_hours')),
            })

            recs.unlink()
    def _get_email_template(self):
        return self.env.ref('attendances.emapproval_line')

    def action_send_email(self):
        template = self._get_email_template()
        sent_count = 0
        for line in self.line_ids:
            if (
                    not line.employee_name or
                    not line.employee_name.work_email or
                    line.total_deduction <= 0
            ):
                continue

            template.with_context(
                email_to=line.employee_name.work_email,
                employee_name=line.employee_name.name,
                default_model='hr.attendance.report.line',
                default_res_id=line.id,
            ).send_mail(line.id, force_send=True)

            sent_count += 1

        self.send_email = True
        return sent_count


