import datetime

from odoo import _, api, fields, models


def _xeno_format_leave_period(number_of_days, request_unit, hour_from, hour_to):
    """Python port of the Dashboard's own JS _formatLeavePeriod() (dashboard_
    action.js) -- an email can't load browser-side JS, so this is kept in
    sync by hand, same convention as xeno_leave's Analysis checklist email
    port. Must stay word-for-word equivalent to the JS version."""
    if request_unit == "first_half":
        return _("Half Day - AM")
    if request_unit == "second_half":
        return _("Half Day - PM")
    if request_unit == "part_time":
        def fmt12(hour_float):
            if not hour_float and hour_float != 0:
                return ""
            h24 = int(hour_float)
            m = round((hour_float - h24) * 60)
            period = "am" if h24 < 12 else "pm"
            h12 = h24 % 12 or 12
            return "%d:%02d %s" % (h12, m, period)
        f, t = fmt12(hour_from), fmt12(hour_to)
        return f"{f} - {t}" if f and t else _("Part Time")
    days = number_of_days or 0
    return _("%s days", round(days)) if days > 1 else _("1 day")


class XenoHrDashboard(models.AbstractModel):
    _name = "xeno.hr.dashboard"
    _description = (
        "Admin/HR overview dashboard data, mirrors XENHR's "
        "DashboardController::adminDashboard. No stored data of its own -- "
        "a thin aggregation layer over hr.employee/hr.leave/"
        "xeno.attendance.report so the Dashboard, System Access and "
        "Monthly Attendance screens never disagree on the same numbers."
    )

    @api.model
    def get_admin_stats(self):
        """Stat cards + weekly bar chart. 'Present' for a day = at least
        one scan (app or biometric, already merged by
        xeno.attendance.report's HRSystem SP call) OR an auto-fill-exempt
        employee on a working day -- exactly the same "present" xeno_
        attendance's own Monthly/My Attendance pages use, on purpose.
        'Absent' is XENHR's own (unadjusted) total-minus-present, matching
        DashboardController::adminDashboard exactly rather than "fixing" it
        to exclude leave/weekends -- that's a real XENHR behavior, not a
        bug, and this dashboard is meant to read the same as XENHR did."""
        today = datetime.date.today()
        total_employees = self.env["hr.employee"].search_count([("active", "=", True)])
        pending_leaves = self.env["hr.leave"].search_count(
            [("state", "in", ("confirm", "validate1"))])

        Report = self.env["xeno.attendance.report"]
        AutoRule = self.env["xeno.attendance.auto.rule"]
        auto_fill_codes = AutoRule.sudo().get_auto_fill_codes()

        month_cache = {}
        weekly = []
        for delta in range(6, -1, -1):
            day = today - datetime.timedelta(days=delta)
            key = (day.year, day.month)
            if key not in month_cache:
                month_cache[key] = Report.sudo().get_month_report(*key)
            data = month_cache[key]
            datestr = day.strftime("%Y-%m-%d")
            is_weekend = day.weekday() >= 5
            is_holiday = datestr in data["holidays"]

            present_codes = set()
            for row in data["rows"]:
                code = str(row.get("employee_code") or "")
                if row.get("%s.In" % datestr):
                    present_codes.add(code)
            if not is_weekend and not is_holiday:
                present_codes |= auto_fill_codes

            present = len(present_codes)
            weekly.append({
                "date": datestr,
                "day": day.strftime("%a"),
                "present": present,
                "absent": max(total_employees - present, 0),
            })

        today_entry = weekly[-1]
        return {
            "total_employees": total_employees,
            "present_today": today_entry["present"],
            "absent_today": today_entry["absent"],
            "pending_leaves": pending_leaves,
            "weekly_attendance": weekly,
        }

    @api.model
    def get_leave_attendance_rows(self, date_str):
        """Server-side port of the Dashboard's own "Employee Attendance
        Report" widget query (dashboard_action.js's loadLeaveAttendance()) --
        approved/pending leaves covering date_str. Used by the daily email
        cron (hr_dashboard_email.py) so the emailed table is built from the
        exact same rule as what HR sees live on the page; kept in sync by
        hand since the widget itself stays client-side JS."""
        leaves = self.env["hr.leave"].sudo().search_read(
            [
                ("state", "in", ("confirm", "validate1", "validate")),
                ("request_date_from", "<=", date_str),
                ("request_date_to", ">=", date_str),
            ],
            ["employee_id", "holiday_status_id", "request_date_from", "request_date_to",
             "number_of_days", "xeno_request_unit", "request_hour_from", "request_hour_to", "state"],
        )
        emp_ids = list({l["employee_id"][0] for l in leaves if l["employee_id"]})
        emp_by_id = {
            e["id"]: e for e in self.env["hr.employee"].sudo().search_read(
                [("id", "in", emp_ids)], ["name", "department_id", "job_title"])
        } if emp_ids else {}

        def fmt_date(d):
            return d.strftime("%d %b %Y")

        rows = []
        for leave in leaves:
            emp = emp_by_id.get(leave["employee_id"][0], {})
            leave_from = fields.Date.from_string(leave["request_date_from"])
            back_to_work = fields.Date.from_string(leave["request_date_to"]) + datetime.timedelta(days=1)
            rows.append({
                "name": emp.get("name") or leave["employee_id"][1],
                "position": emp.get("job_title") or "-",
                "department": emp["department_id"][1] if emp.get("department_id") else "-",
                "leave_type": leave["holiday_status_id"][1] if leave["holiday_status_id"] else "-",
                "total_leave_days": _xeno_format_leave_period(
                    leave["number_of_days"], leave["xeno_request_unit"],
                    leave["request_hour_from"], leave["request_hour_to"]),
                "leave_date": fmt_date(leave_from),
                "back_to_work_date": fmt_date(back_to_work),
                "approved": leave["state"] == "validate",
            })

        # Acknowledged/Pending Business Trips (xeno_attendance's
        # hr.business.trip, which this module already depends on) merged in
        # as "leave-like" rows tagged "Business Trip . BT" -- the email's
        # own _leave_type_font_color (leave_email.py, xeno_leave) already
        # has a "BT" entry, so no separate color path is needed here.
        trips = self.env["hr.business.trip"].sudo().search_read(
            [
                ("state", "in", ("confirm", "acknowledged")),
                ("date_from", "<=", date_str),
                ("date_to", ">=", date_str),
            ],
            ["employee_id", "date_from", "date_to", "number_of_days", "state"],
        )
        trip_emp_ids = list({t["employee_id"][0] for t in trips if t["employee_id"]})
        trip_emp_by_id = {
            e["id"]: e for e in self.env["hr.employee"].sudo().search_read(
                [("id", "in", trip_emp_ids)], ["name", "department_id", "job_title"])
        } if trip_emp_ids else {}
        for trip in trips:
            emp = trip_emp_by_id.get(trip["employee_id"][0], {})
            trip_from = fields.Date.from_string(trip["date_from"])
            back_to_work = fields.Date.from_string(trip["date_to"]) + datetime.timedelta(days=1)
            rows.append({
                "name": emp.get("name") or trip["employee_id"][1],
                "position": emp.get("job_title") or "-",
                "department": emp["department_id"][1] if emp.get("department_id") else "-",
                "leave_type": "Business Trip . BT",
                "total_leave_days": _xeno_format_leave_period(trip["number_of_days"], "full_day", None, None),
                "leave_date": fmt_date(trip_from),
                "back_to_work_date": fmt_date(back_to_work),
                "approved": trip["state"] == "acknowledged",
            })
        return rows
