from odoo import _, api, fields, models
from odoo.exceptions import ValidationError


class XenoLeaveApprover(models.Model):
    _name = "xeno.leave.approver"
    _description = "Approval chain step template (per department or per employee)"
    _order = "employee_id, department_id, step_order"

    employee_id = fields.Many2one(
        "hr.employee", string="Employee (specific override)",
        help="If set, this step belongs to this employee's override chain, "
             "which fully replaces the department chain when it has any "
             "active steps.")
    department_id = fields.Many2one(
        "hr.department", string="Department (default chain)",
        help="Applies to every employee in this department who has no "
             "employee-specific override chain.")
    step_order = fields.Integer(
        string="Step", default=1, required=True,
        help="Order this step is acted on in, within this employee's or "
             "department's chain. Lower runs first.")
    label = fields.Char(
        required=True, default="Approver",
        help="e.g. 'Department Head', 'Manager'.")
    user_id = fields.Many2one(
        "res.users", string="Approver",
        help="Specific approver for this step. Leave blank for 'any Time "
             "Off Officer/Administrator may act' (matches XENHR's nullable "
             "approver_user_id).")
    active = fields.Boolean(default=True)
    note = fields.Char(help="Optional context, e.g. 'covers for X while on leave'.")

    _step_order_positive = models.Constraint(
        "CHECK(step_order > 0)", "Step must be 1 or higher.",
    )

    @api.constrains("employee_id", "department_id")
    def _check_target(self):
        for rec in self:
            if not rec.employee_id and not rec.department_id:
                raise ValidationError(_(
                    "Set either an Employee or a Department for this "
                    "approval step (or both, for an employee override that "
                    "also records which department it belongs to)."
                ))

    def name_get(self):
        out = []
        for rec in self:
            target = rec.employee_id.name or rec.department_id.name or "?"
            approver = rec.user_id.name or "Any HR/Admin"
            out.append((rec.id, f"{target} · step {rec.step_order} → {approver}"))
        return out

    @api.model
    def xeno_is_current_user_any_approver(self):
        """True if this user could ever be asked to decide someone's leave:
        either named as a specific step's approver on some chain, or an
        Officer/Admin (who may act on any blank-approver step, including
        the synthetic final "HR Manager" step build_chain appends to every
        chain). Mirrors the same two branches hr.leave's
        _xeno_step_acting_user decides a single step by.

        Exists so callers can hide an approval entry point up front rather
        than send a non-approver into an empty page -- deliberately reads
        only xeno.leave.approver, which every internal user has read access
        to (security/ir.model.access.csv), never hr.leave.
        """
        if self.env.user.has_group("hr_holidays.group_hr_holidays_user"):
            return True
        return bool(self.search_count([("user_id", "=", self.env.uid)]))

    @api.model
    def build_chain(self, employee):
        """Resolve the approval chain that will be materialized when this
        employee submits a leave, mirroring XENHR's
        ApproverConfigController::buildChain exactly:
        1. Employee-level override steps (active, ordered) if any exist --
           these fully REPLACE the department chain, they are never merged
           with it.
        2. Otherwise the department's active steps, ordered.
        3. Always append a final synthetic "HR Manager" step with no
           specific approver (any Time Off Officer/Administrator may act).

        Returns a list of dicts: step_order, label, approver_user_id (int
        or None). Does not touch the database.

        employee may be a real hr.employee recordset (existing internal
        callers, e.g. hr_leave.py's _xeno_build_approval_chain) or a plain
        employee id (the Approver Configuration page's Preview button
        calls this directly via orm.call, which never auto-casts a bare
        int argument into a recordset)."""
        if isinstance(employee, int):
            employee = self.env["hr.employee"].browse(employee)

        steps = []
        order = 1

        emp_rules = self.search([
            ("employee_id", "=", employee.id), ("active", "=", True),
        ], order="step_order")

        if emp_rules:
            for rule in emp_rules:
                steps.append({
                    "step_order": order,
                    "label": rule.label,
                    "approver_user_id": rule.user_id.id or None,
                })
                order += 1
        elif employee.department_id:
            dept_rules = self.search([
                ("department_id", "=", employee.department_id.id),
                ("employee_id", "=", False),
                ("active", "=", True),
            ], order="step_order")
            for rule in dept_rules:
                steps.append({
                    "step_order": order,
                    "label": rule.label,
                    "approver_user_id": rule.user_id.id or None,
                })
                order += 1

        steps.append({
            "step_order": order,
            "label": "HR Manager",
            "approver_user_id": None,
        })
        return steps

    @api.model
    def save_department_chain(self, department_id, steps):
        """Full replace, matching XENHR's saveDepartmentApprovers exactly:
        delete every existing step for this department, then recreate from
        the submitted list in order. steps is a list of dicts with
        "label" and "user_id" (falsy/None = any HR/Admin)."""
        self.search([
            ("department_id", "=", department_id), ("employee_id", "=", False),
        ]).unlink()
        for i, step in enumerate(steps, start=1):
            self.create({
                "department_id": department_id,
                "step_order": i,
                "label": step["label"],
                "user_id": step.get("user_id") or False,
            })
        return True

    @api.model
    def save_employee_chain(self, employee_id, steps):
        """Full replace for one employee's override chain. An empty steps
        list removes the override entirely, reverting that employee to
        their department's default chain -- matches XENHR's
        saveEmployeeApprovers "steps is empty -> remove all overrides"
        branch exactly."""
        self.search([("employee_id", "=", employee_id)]).unlink()
        for i, step in enumerate(steps, start=1):
            self.create({
                "employee_id": employee_id,
                "step_order": i,
                "label": step["label"],
                "user_id": step.get("user_id") or False,
            })
        return True
