import base64
import io

import xlsxwriter
from markupsafe import escape

from odoo import api, fields, models

REMAINING_COLOR = "#206b45"
USED_COLOR = "#a33a49"


class ResCompany(models.Model):
    _inherit = "res.company"

    xeno_monthly_report_recipient_ids = fields.Many2many(
        "res.users", "res_company_xeno_monthly_report_recipient_rel",
        "company_id", "user_id", string="Monthly HR Report Recipients",
        domain=[("share", "=", False)], groups="hr.group_hr_manager",
        help="Who receives the automated Leave Balance + Attendance report "
             "email, sent on the 16th of every month. No redeploy needed to "
             "change the list -- just update this and it takes effect on "
             "the next run.",
    )


class XenoMonthlyHrReport(models.AbstractModel):
    """Builds and emails the automated monthly Leave Balance + Attendance
    report set. Reuses xeno.attendance.report (xeno_attendance) for the
    Attendance Report entirely -- same render_html/export_xlsx already used
    by the Monthly Attendance page, so there's exactly one place that knows
    how to read HRSystem's attendance data. The Leave Balance Report is
    built here (allocation data lives in this module), mirroring
    payroll_export.py's xlsx/HTML-to-PDF pattern.
    """

    _name = "xeno.monthly.hr.report"
    _description = "Automated Monthly HR Report"

    @api.model
    def _cron_xeno_send_monthly_reports(self):
        """Daily-checked, date-gated cron (same idiom as
        xeno.late.deduction's own cron): only actually runs on the 16th, so
        scheduling is a plain daily interval -- no reliance on ir.cron's
        own day-of-month precision. Idempotent isn't strictly needed here
        (unlike late-deduction, which touches leave balances) since this
        only sends an email; a re-run on the same day would just re-send,
        which is an acceptable, easily-noticed edge case rather than one
        worth adding extra guard state for.
        """
        today = fields.Date.context_today(self)
        if today.day != 16:
            return
        for company in self.env["res.company"].sudo().search([]):
            recipients = company.xeno_monthly_report_recipient_ids.mapped("email")
            recipients = [e for e in recipients if e]
            if not recipients:
                continue
            self.with_company(company)._xeno_send_monthly_reports(today, recipients)

    def _xeno_send_monthly_reports(self, today, recipients):
        # Attendance covers the just-ended month (mirrors the late-deduction
        # cron's own "processing the month just ended" convention); Leave
        # Balance is a live snapshot as of send time, not period-bound.
        att_year, att_month = self._xeno_previous_month(today)

        AttReport = self.env["xeno.attendance.report"].sudo()
        att_xlsx = AttReport.export_xlsx(att_year, att_month, None)
        att_html = AttReport.render_html(att_year, att_month, None, can_edit=False)
        att_pdf = self.env["ir.actions.report"].sudo()._run_wkhtmltopdf([att_html], landscape=True)

        leave_xlsx, leave_pdf = self._xeno_build_leave_balance_report(today)

        month_label = "%04d-%02d" % (att_year, att_month)
        Attachment = self.env["ir.attachment"].sudo()
        attachment_ids = [
            Attachment.create({
                "name": "Leave_Balance_Report_%s.xlsx" % today,
                "datas": self._b64(leave_xlsx), "type": "binary",
            }).id,
            Attachment.create({
                "name": "Leave_Balance_Report_%s.pdf" % today,
                "datas": self._b64(leave_pdf), "type": "binary",
            }).id,
            Attachment.create({
                "name": "Attendance_Report_%s.xlsx" % month_label,
                "datas": self._b64(att_xlsx), "type": "binary",
            }).id,
            Attachment.create({
                "name": "Attendance_Report_%s.pdf" % month_label,
                "datas": self._b64(att_pdf), "type": "binary",
            }).id,
        ]
        self.env["mail.mail"].sudo().create({
            "subject": "Monthly HR Report - %s" % today,
            "body_html": (
                "<p>Attached: this month's Leave Balance Report (snapshot as of %s) "
                "and the Attendance Report for %s.</p>"
                "<p>Automated monthly email -- xeno_leave.</p>" % (today, month_label)
            ),
            "email_to": ",".join(recipients),
            "attachment_ids": [(6, 0, attachment_ids)],
            "auto_delete": True,
        }).send()

    @staticmethod
    def _b64(data):
        return base64.b64encode(data)

    @staticmethod
    def _xeno_previous_month(today):
        year, month = today.year, today.month - 1
        if month == 0:
            year, month = year - 1, 12
        return year, month

    # ---------------------------------------------------- leave balance

    def _xeno_build_leave_balance_report(self, today):
        leave_types = self.env["hr.leave.type"].sudo().search([], order="sequence, id")
        employees = self.env["hr.employee"].sudo().search([("active", "=", True)], order="name")
        allocations = self.env["hr.leave.allocation"].sudo().search([
            ("employee_id", "in", employees.ids),
            ("holiday_status_id", "in", leave_types.ids),
            ("state", "=", "validate"),
        ])
        by_key = {}
        for alloc in allocations:
            by_key[(alloc.employee_id.id, alloc.holiday_status_id.id)] = alloc

        rows = []
        for emp in employees:
            cells = []
            has_any = False
            for lt in leave_types:
                alloc = by_key.get((emp.id, lt.id))
                if alloc:
                    has_any = True
                    cells.append({
                        "allocated": alloc.number_of_days or 0.0,
                        "used": alloc.xeno_used_days or 0.0,
                        "remaining": alloc.xeno_remaining_days or 0.0,
                    })
                else:
                    cells.append(None)
            if has_any:
                rows.append({
                    "employee_code": emp.xeno_employee_code or "",
                    "employee_name": emp.name,
                    "cells": cells,
                })

        xlsx = self._xeno_leave_balance_xlsx(leave_types, rows, today)
        pdf = self._xeno_leave_balance_pdf(leave_types, rows, today)
        return xlsx, pdf

    def _xeno_leave_balance_xlsx(self, leave_types, rows, today):
        output = io.BytesIO()
        workbook = xlsxwriter.Workbook(output, {"in_memory": True})
        sheet = workbook.add_worksheet("Leave Balances")

        header_fmt = workbook.add_format({
            "bold": True, "bg_color": "#fafafa", "border": 1,
            "align": "center", "valign": "vcenter",
        })
        code_fmt = workbook.add_format({"border": 1})
        name_fmt = workbook.add_format({"bold": True, "border": 1})
        num_fmt = workbook.add_format({"border": 1, "align": "right"})
        used_fmt = workbook.add_format({"border": 1, "align": "right", "font_color": USED_COLOR})
        remaining_fmt = workbook.add_format({
            "border": 1, "align": "right", "font_color": REMAINING_COLOR, "bold": True})

        sheet.merge_range(0, 0, 1, 0, "Employee Code", header_fmt)
        sheet.merge_range(0, 1, 1, 1, "Employee", header_fmt)
        col = 2
        for lt in leave_types:
            sheet.merge_range(0, col, 0, col + 2, lt.name, header_fmt)
            sheet.write(1, col, "Allocated", header_fmt)
            sheet.write(1, col + 1, "Used", header_fmt)
            sheet.write(1, col + 2, "Remaining", header_fmt)
            col += 3
        sheet.set_column(0, 0, 14)
        sheet.set_column(1, 1, 24)
        if leave_types:
            sheet.set_column(2, col - 1, 11)

        r = 2
        for row in rows:
            sheet.write(r, 0, row["employee_code"], code_fmt)
            sheet.write(r, 1, row["employee_name"], name_fmt)
            c = 2
            for cell in row["cells"]:
                if cell:
                    sheet.write(r, c, cell["allocated"], num_fmt)
                    sheet.write(r, c + 1, cell["used"], used_fmt)
                    sheet.write(r, c + 2, cell["remaining"], remaining_fmt)
                else:
                    sheet.write(r, c, "-", num_fmt)
                    sheet.write(r, c + 1, "-", num_fmt)
                    sheet.write(r, c + 2, "-", num_fmt)
                c += 3
            r += 1

        sheet.freeze_panes(2, 2)
        workbook.close()
        output.seek(0)
        return output.read()

    def _xeno_leave_balance_pdf(self, leave_types, rows, today):
        type_headers = "".join(
            '<th colspan="3">%s</th>' % escape(lt.name) for lt in leave_types)
        sub_headers = "<th>Alloc.</th><th>Used</th><th>Rem.</th>" * len(leave_types)

        body_rows = []
        for row in rows:
            cells = []
            for cell in row["cells"]:
                if cell:
                    cells.append(
                        '<td class="num">%.2f</td><td class="num used">%.2f</td>'
                        '<td class="num remaining">%.2f</td>' % (
                            cell["allocated"], cell["used"], cell["remaining"]))
                else:
                    cells.append('<td class="num">-</td><td class="num">-</td><td class="num">-</td>')
            body_rows.append(
                '<tr><td class="code">%s</td><td class="name">%s</td>%s</tr>' % (
                    escape(row["employee_code"]), escape(row["employee_name"]), "".join(cells)))

        html = """<!DOCTYPE html><html><head><meta charset="utf-8"/><style>
            body { font-family: Arial, Helvetica, sans-serif; font-size: 9px; }
            h2 { margin: 0 0 12px; color: #1a2b4a; }
            table { border-collapse: collapse; width: 100%%; }
            th, td { border: 1px solid #ccc; padding: 3px 6px; }
            th { background: #fafafa; text-align: center; }
            td.name { font-weight: bold; }
            td.code { color: #6b6b6b; }
            td.num { text-align: right; }
            td.used { color: %s; }
            td.remaining { color: %s; font-weight: bold; }
        </style></head><body>
            <h2>Leave Balance Report &mdash; snapshot as of %s</h2>
            <table>
                <thead>
                    <tr><th rowspan="2">Employee Code</th><th rowspan="2">Employee</th>%s</tr>
                    <tr>%s</tr>
                </thead>
                <tbody>%s</tbody>
            </table>
        </body></html>""" % (
            USED_COLOR, REMAINING_COLOR, today, type_headers, sub_headers, "".join(body_rows))

        return self.env["ir.actions.report"].sudo()._run_wkhtmltopdf([html], landscape=True)
