# -*- coding: utf-8 -*-
"""STEP 4 — Deadline engine.

A family-law deadline is a statutory/procedural clock. It attaches to a
PROCEEDING (locked decision — so the 2024 dissolution's disclosure clock never
collides with a 2027 modification's), computes its due date deterministically with
Florida-style weekend roll-forward, mirrors itself onto the shared calendar, and is
flagged overdue by a daily cron.

DATE-MATH NOTE (verify current rule at build time):
  Florida Rule of Judicial Administration 2.514 computes day-periods by excluding
  the triggering day, counting every following day, and — if the last day falls on a
  Saturday, Sunday, or legal holiday — rolling forward to the next day that is none
  of those. We implement the exclude-trigger + weekend roll here. LEGAL HOLIDAYS are
  jurisdiction- and year-specific and are intentionally NOT hardcoded; see
  _holiday_dates() for the hook. Standard day-counts (20/45/30/10) are common values
  and MUST be confirmed against the current rule for each matter type.
"""

from datetime import timedelta

from odoo import api, fields, models, _

# Common Florida day-counts. VERIFY CURRENT RULE per matter type — do not treat as
# permanent. Used as defaults; the `days` field can override per record.
STANDARD_OFFSETS = {
    "answer": 20,        # Respondent's answer window (Rule 12.140 family practice)
    "disclosure": 45,    # Mandatory disclosure (Rule 12.285)
    "discovery": 30,     # Interrogatory / production responses (Rule 12.340/12.350)
    "objection": 10,     # Subpoena objection window (Step 8)
}


class FamilyLawDeadline(models.Model):
    _name = "familylaw.deadline"
    _description = "Procedural Deadline"
    _inherit = ["mail.thread", "mail.activity.mixin"]
    _order = "due_date asc"

    name = fields.Char(required=True, tracking=True)
    proceeding_id = fields.Many2one(
        "familylaw.proceeding",
        string="Proceeding",
        required=True,
        ondelete="cascade",
        index=True,
        tracking=True,
        help="Deadlines attach to a proceeding, not directly to the case.",
    )
    case_id = fields.Many2one(
        "familylaw.case",
        related="proceeding_id.case_id",
        store=True,
        index=True,
    )
    deadline_type = fields.Selection(
        selection=[
            ("answer", "Answer / Response"),
            ("disclosure", "Mandatory Disclosure"),
            ("discovery", "Discovery Response"),
            ("objection", "Objection Window"),
            ("hearing", "Hearing"),
            ("custom", "Custom"),
        ],
        string="Type",
        required=True,
        default="custom",
        tracking=True,
    )
    trigger_date = fields.Date(
        string="Trigger Date",
        required=True,
        tracking=True,
        help="The event that starts the clock (e.g. date of service). Excluded from "
             "the count per Rule 2.514.",
    )
    days = fields.Integer(
        string="Days",
        required=True,
        default=0,
        tracking=True,
        help="Number of days in the period. Defaults from the type but can be "
             "overridden. Verify the current rule.",
    )
    due_date = fields.Date(
        string="Due Date",
        compute="_compute_due_date",
        store=True,
        tracking=True,
    )
    state = fields.Selection(
        selection=[
            ("pending", "Pending"),
            ("done", "Completed"),
            ("waived", "Waived"),
            ("overdue", "Overdue"),
        ],
        default="pending",
        required=True,
        copy=False,
        tracking=True,
    )
    is_overdue = fields.Boolean(
        compute="_compute_is_overdue",
        search="_search_is_overdue",
        string="Overdue?",
    )
    calendar_event_id = fields.Many2one(
        "calendar.event",
        string="Calendar Entry",
        copy=False,
        readonly=True,
    )
    notes = fields.Text()

    # --- date math ----------------------------------------------------------
    def _holiday_dates(self):
        """Hook for legal holidays. Intentionally empty — holidays are
        jurisdiction/year specific and must be supplied by the content-maintenance
        process (Requirements for Success #7). Return a set of date objects."""
        return set()

    def _roll_forward(self, due):
        """Roll a due date off weekends (and holidays, when supplied)."""
        holidays = self._holiday_dates()
        while due.weekday() >= 5 or due in holidays:
            due += timedelta(days=1)
        return due

    @api.depends("trigger_date", "days")
    def _compute_due_date(self):
        for dl in self:
            if not dl.trigger_date or not dl.days:
                dl.due_date = dl.trigger_date or False
                continue
            raw = dl.trigger_date + timedelta(days=dl.days)
            dl.due_date = dl._roll_forward(raw)

    @api.depends("due_date", "state")
    def _compute_is_overdue(self):
        today = fields.Date.context_today(self)
        for dl in self:
            dl.is_overdue = bool(
                dl.due_date
                and dl.state == "pending"
                and dl.due_date < today
            )

    def _search_is_overdue(self, operator, value):
        if operator not in ("=", "!="):
            raise ValueError(_("Unsupported operator for is_overdue search."))
        today = fields.Date.context_today(self)
        want_overdue = (operator == "=" and value) or (operator == "!=" and not value)
        if want_overdue:
            return [("state", "=", "pending"), ("due_date", "<", today)]
        return ["|", ("state", "!=", "pending"), ("due_date", ">=", today)]

    # --- defaults from type -------------------------------------------------
    @api.onchange("deadline_type")
    def _onchange_deadline_type(self):
        if self.deadline_type in STANDARD_OFFSETS and not self.days:
            self.days = STANDARD_OFFSETS[self.deadline_type]

    @api.model_create_multi
    def create(self, vals_list):
        for vals in vals_list:
            if not vals.get("days") and vals.get("deadline_type") in STANDARD_OFFSETS:
                vals["days"] = STANDARD_OFFSETS[vals["deadline_type"]]
        records = super().create(vals_list)
        records._sync_calendar_event()
        return records

    def write(self, vals):
        res = super().write(vals)
        if {"name", "due_date", "trigger_date", "days", "state"} & set(vals):
            self._sync_calendar_event()
        return res

    # --- calendar mirror ----------------------------------------------------
    def _sync_calendar_event(self):
        Cal = self.env["calendar.event"]
        for dl in self:
            if not dl.due_date:
                continue
            title = _("[Deadline] %(name)s — %(matter)s",
                      name=dl.name, matter=dl.case_id.name or "")
            vals = {
                "name": title,
                "allday": True,
                "start_date": dl.due_date,
                "stop_date": dl.due_date,
            }
            if dl.calendar_event_id:
                dl.calendar_event_id.write(vals)
            else:
                dl.calendar_event_id = Cal.create(vals).id

    # --- actions ------------------------------------------------------------
    def action_mark_done(self):
        for dl in self:
            dl.state = "done"
            dl.message_post(body=_("Deadline completed."))
        return True

    def action_waive(self):
        for dl in self:
            dl.state = "waived"
            dl.message_post(body=_("Deadline waived."))
        return True

    def action_reset_pending(self):
        for dl in self:
            dl.state = "pending"
            dl.message_post(body=_("Deadline reset to pending."))
        return True

    # --- cron ---------------------------------------------------------------
    @api.model
    def _cron_flag_overdue(self):
        """Daily: flag pending deadlines whose due date has passed."""
        today = fields.Date.context_today(self)
        overdue = self.search([
            ("state", "=", "pending"),
            ("due_date", "<", today),
        ])
        for dl in overdue:
            dl.state = "overdue"
            dl.message_post(
                body=_("Deadline is OVERDUE (due %s) — flagged by the deadline engine.")
                % dl.due_date
            )
        return len(overdue)
