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

TRIP_TYPES = [
    ("exhibition", "Exhibition"),
    ("supplier_visit", "Supplier Visit"),
    ("customer_visit", "Customer Visit"),
    ("demo", "Demo"),
    ("service", "Service"),
    ("others", "Others"),
]

STATES = [
    ("draft", "Draft"),
    ("confirm", "Pending HR"),
    ("acknowledged", "Acknowledged"),
    ("rejected", "Rejected"),
]


class HrBusinessTrip(models.Model):
    """Employee request for HR to prepare/authorize a business trip. Once HR
    Acknowledges it, the employee is exempt from check-in/out for the whole
    date_from..date_to range and it shows as "BT" on the Monthly Attendance
    report (attendance_report.py threads it through the same way as
    xeno.attendance.dayoff) and as a "Business Trip . BT" row on the
    Employee Attendance Report / daily attendance email (xeno_leave's
    xeno_get_attendance_report and xeno_hr_dashboard's
    get_leave_attendance_rows -- both modules already depend on this one)."""

    _name = "hr.business.trip"
    _description = "Business Trip Request"
    _inherit = ["mail.thread"]
    _order = "date_from desc, id desc"

    name = fields.Char(compute="_compute_name", store=True)
    employee_id = fields.Many2one(
        "hr.employee", required=True, tracking=True,
        default=lambda self: self.env.user.employee_id.id)
    employee_code = fields.Char(
        related="employee_id.xeno_employee_code", store=True, index=True,
        help="HRSystem employee_code -- the join key the Monthly Attendance "
             "report matches this request against.")

    date_from = fields.Date(string="Start Date", required=True, tracking=True)
    date_to = fields.Date(string="End Date", required=True, tracking=True)
    number_of_days = fields.Integer(compute="_compute_number_of_days")

    trip_type = fields.Selection(TRIP_TYPES, string="Type", required=True, tracking=True)
    trip_type_other = fields.Char(
        string="Other Type",
        help='Required when Type is "Others" -- shown on the report tooltip and requests list.')

    address = fields.Char()
    city = fields.Char()
    country_id = fields.Many2one("res.country", string="Country")

    reason = fields.Text()
    requirements_description = fields.Text(string="Requirements")
    remark = fields.Text()

    state = fields.Selection(STATES, default="draft", required=True, tracking=True, copy=False)
    rejection_reason = fields.Char(copy=False)
    hr_decided_by = fields.Many2one("res.users", readonly=True, copy=False)
    hr_decided_at = fields.Datetime(readonly=True, copy=False)

    can_submit_request = fields.Boolean(compute="_compute_can_flags")
    can_hr_decide = fields.Boolean(compute="_compute_can_flags")

    @api.depends_context("uid")
    @api.depends("employee_id", "state")
    def _compute_can_flags(self):
        is_hr = self._xeno_is_hr()
        for rec in self:
            is_owner = self.env.user.employee_id == rec.employee_id
            rec.can_submit_request = (is_hr or is_owner) and rec.state == "draft"
            rec.can_hr_decide = is_hr

    @api.depends("employee_id", "date_from", "date_to")
    def _compute_name(self):
        for rec in self:
            rec.name = _(
                "%(employee)s - Business Trip %(date_from)s to %(date_to)s",
                employee=rec.employee_id.name or "",
                date_from=rec.date_from or "", date_to=rec.date_to or "")

    @api.depends("date_from", "date_to")
    def _compute_number_of_days(self):
        for rec in self:
            rec.number_of_days = (
                (rec.date_to - rec.date_from).days + 1
                if rec.date_from and rec.date_to and rec.date_to >= rec.date_from else 0)

    @api.constrains("date_from", "date_to")
    def _check_dates(self):
        for rec in self:
            if rec.date_from and rec.date_to and rec.date_to < rec.date_from:
                raise ValidationError(_("End Date must be on or after Start Date."))

    @api.constrains("trip_type", "trip_type_other")
    def _check_other_type(self):
        for rec in self:
            if rec.trip_type == "others" and not (rec.trip_type_other or "").strip():
                raise ValidationError(_('Please specify the type when "Others" is selected.'))

    def _xeno_is_hr(self):
        return self.env.is_superuser() or self.env.user.has_group("hr.group_hr_user")

    # ------------------------------------------------------------ create/write

    @api.model_create_multi
    def create(self, vals_list):
        # Same self-service guard as xeno_overtime's hr.overtime.request:
        # a regular employee can only ever create a request for themselves.
        is_hr = self.env.su or self._xeno_is_hr()
        own_employee_id = self.env.user.employee_id.id
        if not is_hr:
            for vals in vals_list:
                target_employee_id = vals.get("employee_id", own_employee_id)
                if target_employee_id != own_employee_id:
                    raise UserError(_(
                        "You can only create a Business Trip request for yourself. "
                        "Ask HR to submit one on your behalf if needed."))
        return super().create(vals_list)

    _XENO_REQUEST_FIELDS = {
        "date_from", "date_to", "trip_type", "trip_type_other", "address",
        "city", "country_id", "reason", "requirements_description", "remark",
    }
    _XENO_TRANSITION_FIELDS = {"state", "hr_decided_by", "hr_decided_at", "rejection_reason"}

    def write(self, vals):
        if not self.env.su and not self._xeno_is_hr():
            touched = set(vals)
            # Only the vetted action_* methods (which sudo() internally
            # after their own checks) may move the state machine forward.
            if touched & self._XENO_TRANSITION_FIELDS:
                raise UserError(_(
                    "Use Submit/Acknowledge/Reject to change a Business Trip "
                    "request's status -- it can't be edited directly."))
            if "employee_id" in touched:
                raise UserError(_(
                    "The employee on a Business Trip request can't be changed. "
                    "Ask HR if this needs to be corrected."))
            for rec in self:
                if touched & self._XENO_REQUEST_FIELDS:
                    if rec.state != "draft":
                        raise UserError(_(
                            "This request can no longer be edited once it's been "
                            "submitted. Ask HR to update it."))
                    if rec.env.user.employee_id != rec.employee_id:
                        raise UserError(_("Only the requesting employee can edit this request."))
        return super().write(vals)

    def unlink(self):
        if not self.env.su and not self._xeno_is_hr():
            for rec in self:
                if rec.state != "draft" or rec.env.user.employee_id != rec.employee_id:
                    raise UserError(_(
                        "Only your own still-Draft request can be deleted; "
                        "once submitted, ask HR."))
        return super().unlink()

    # ------------------------------------------------------------ actions

    def action_submit(self):
        for rec in self:
            if rec.state != "draft":
                raise UserError(_("Only a Draft request can be submitted."))
            if not rec.can_submit_request:
                raise UserError(_("Only the requesting employee can submit this request."))
            rec.sudo().write({"state": "confirm"})
            rec.message_post(body=_("Submitted for HR acknowledgment."))

    def action_acknowledge(self):
        for rec in self:
            if rec.state != "confirm":
                raise UserError(_("Only a Pending request can be acknowledged."))
            if not rec._xeno_is_hr():
                raise UserError(_("Only HR can acknowledge this request."))
            rec.sudo().write({
                "state": "acknowledged",
                "hr_decided_by": self.env.user.id,
                "hr_decided_at": fields.Datetime.now(),
            })
            rec.message_post(body=_("Acknowledged by HR: %s", self.env.user.name))

    def action_reject(self, reason=False):
        for rec in self:
            if rec.state != "confirm":
                raise UserError(_("Only a Pending request can be rejected."))
            if not rec._xeno_is_hr():
                raise UserError(_("Only HR can reject this request."))
            rec.sudo().write({"state": "rejected", "rejection_reason": reason or False})
            rec.message_post(body=_("Rejected by HR: %s", self.env.user.name))

    def action_reset_to_draft(self):
        for rec in self:
            if rec.state != "rejected":
                raise UserError(_("Only a Rejected request can be reset to Draft."))
            if not (rec._xeno_is_hr() or rec.env.user.employee_id == rec.employee_id):
                raise UserError(_("Only the requesting employee can reset this request."))
            rec.sudo().write({"state": "draft", "rejection_reason": False})
            rec.message_post(body=_("Reset to Draft."))
