# Copyright 2017 Camptocamp SA
# Copyright 2019-20 ForgeFlow S.L. (https://www.forgeflow.com)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from dateutil.relativedelta import relativedelta

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


class MaintenancePlan(models.Model):
    _name = "maintenance.plan"
    _description = "Maintenance Plan"
    _inherit = ["mail.thread", "mail.activity.mixin"]

    name = fields.Char("Description")
    active = fields.Boolean(default=True)
    equipment_id = fields.Many2one(
        string="Equipment", comodel_name="maintenance.equipment", ondelete="cascade"
    )
    company_id = fields.Many2one(
        comodel_name="res.company",
        default=lambda self: self.env.company,
    )
    maintenance_kind_id = fields.Many2one(
        string="Maintenance Kind", comodel_name="maintenance.kind", ondelete="restrict"
    )
    interval = fields.Integer(
        string="Frequency", default=1, help="Interval between each maintenance"
    )
    interval_step = fields.Selection(
        [
            ("day", "Day(s)"),
            ("week", "Week(s)"),
            ("month", "Month(s)"),
            ("year", "Year(s)"),
        ],
        string="Recurrence",
        default="year",
        help="Let the event automatically repeat at that interval step",
    )
    duration = fields.Float(
        string="Duration (hours)", help="Maintenance duration in hours"
    )
    start_maintenance_date = fields.Date(
        default=fields.Date.context_today,
        help="Date from which the maintenance will we active",
    )
    next_maintenance_date = fields.Date(compute="_compute_next_maintenance", store=True)
    maintenance_plan_horizon = fields.Integer(
        string="Planning Horizon period",
        default=1,
        help="Maintenance planning horizon. Only the maintenance requests "
        "inside the horizon will be created.",
    )
    planning_step = fields.Selection(
        [
            ("day", "Day(s)"),
            ("week", "Week(s)"),
            ("month", "Month(s)"),
            ("year", "Year(s)"),
        ],
        string="Planning Horizon step",
        default="year",
        help="Let the event automatically repeat at that interval",
    )
    instruction_type = fields.Selection(
        [("pdf", "PDF"), ("google_slide", "Google Slide"), ("text", "Text")],
        string="Instruction",
        default="text",
    )
    instruction_pdf = fields.Binary("PDF")
    instruction_google_slide = fields.Char(
        "Google Slide",
        help="Paste the url of your Google Slide. "
        "Make sure the access to the document is public.",
    )
    instruction_text = fields.Html("Text")

    maintenance_ids = fields.One2many(
        "maintenance.request", "maintenance_plan_id", string="Maintenance requests"
    )
    maintenance_count = fields.Integer(
        compute="_compute_maintenance_count", string="Maintenance", store=True
    )
    maintenance_open_count = fields.Integer(
        compute="_compute_maintenance_count", string="Current Maintenance", store=True
    )
    maintenance_team_id = fields.Many2one("maintenance.team")
    skip_notify_follower_on_requests = fields.Boolean(
        string="Do not notify to follower when creating requests?", default=True
    )
    generate_with_domain = fields.Boolean()
    generate_domain = fields.Char(string="Apply on")
    search_equipment_id = fields.Many2one(
        comodel_name="maintenance.equipment",
        compute="_compute_search_equipment",
        search="_search_search_equipment",
    )

    @api.model
    def _search_search_equipment(self, operator, value):
        if operator != "=" or (not value and not isinstance(value, models.NewId)):
            raise ValueError(_("Unsupported search operator"))
        plans = self.search([("generate_with_domain", "=", True)])
        plan_ids = []
        equipment = self.env["maintenance.equipment"].browse(value)
        for plan in plans:
            if equipment.filtered_domain(
                safe_eval.safe_eval(
                    plan.generate_domain or "[]", plan._get_eval_context()
                )
            ):
                plan_ids.append(plan.id)
        return ["|", ("equipment_id", "=", value), ("id", "in", plan_ids)]

    @api.depends("equipment_id")
    def _compute_search_equipment(self):
        for record in self:
            record.search_equipment_id = record.equipment_id

    def _get_eval_context(self):
        """Prepare the context used when evaluating python code
        :returns: dict -- evaluation context given to safe_eval
        """
        return {
            "datetime": safe_eval.datetime,
            "dateutil": safe_eval.dateutil,
            "time": safe_eval.time,
        }

    @api.depends("maintenance_kind_id.name", "equipment_id.name")
    def _compute_display_name(self):
        for plan in self:
            plan.display_name = plan.name or _(
                "Unnamed %(kind)s plan (%(eqpmt)s)",
                kind=plan.maintenance_kind_id.name or "",
                eqpmt=plan.equipment_id.name,
            )

    @api.depends("maintenance_ids.stage_id.done")
    def _compute_maintenance_count(self):
        for equipment in self:
            equipment.maintenance_count = len(equipment.maintenance_ids)
            equipment.maintenance_open_count = len(
                equipment.maintenance_ids.filtered(lambda x: not x.stage_id.done)
            )

    def get_relativedelta(self, interval, step):
        if step == "day":
            return relativedelta(days=interval)
        elif step == "week":
            return relativedelta(weeks=interval)
        elif step == "month":
            return relativedelta(months=interval)
        elif step == "year":
            return relativedelta(years=interval)

    @api.depends(
        "interval",
        "interval_step",
        "start_maintenance_date",
        "maintenance_ids.request_date",
        "maintenance_ids.close_date",
    )
    def _compute_next_maintenance(self):
        for plan in self.filtered(lambda x: x.interval > 0):
            interval_timedelta = plan.get_relativedelta(
                plan.interval, plan.interval_step
            )

            next_maintenance_todo = self.env["maintenance.request"].search(
                [
                    ("maintenance_plan_id", "=", plan.id),
                    ("stage_id.done", "!=", True),
                    ("close_date", "=", False),
                    ("request_date", ">=", plan.start_maintenance_date),
                ],
                order="request_date asc",
                limit=1,
            )

            if next_maintenance_todo:
                plan.next_maintenance_date = next_maintenance_todo.request_date
            else:
                last_maintenance_done = self.env["maintenance.request"].search(
                    [
                        ("maintenance_plan_id", "=", plan.id),
                        ("request_date", ">=", plan.start_maintenance_date),
                    ],
                    order="request_date desc",
                    limit=1,
                )
                if last_maintenance_done:
                    plan.next_maintenance_date = (
                        last_maintenance_done.request_date + interval_timedelta
                    )
                else:
                    next_date = plan.start_maintenance_date
                    while next_date < fields.Date.today():
                        next_date = next_date + interval_timedelta
                    plan.next_maintenance_date = next_date

    @api.constrains("company_id", "equipment_id")
    def _check_company_id(self):
        for rec in self:
            if (
                rec.equipment_id.company_id
                and rec.company_id != rec.equipment_id.company_id
            ):
                raise ValidationError(
                    _("Maintenace Equipment must belong to the equipment's company")
                )

    def unlink(self):
        """Restrict deletion of maintenance plan should there be maintenance
        requests of this kind which are not done for its equipment"""
        for plan in self:
            request = plan.equipment_id.mapped("maintenance_ids").filtered(
                lambda r, plan=plan: (
                    r.maintenance_kind_id == plan.maintenance_kind_id
                    and not r.stage_id.done
                    and r.maintenance_type == "preventive"
                )
            )
            if request:
                raise UserError(
                    _(
                        "The maintenance plan %(kind)s of equipment %(eqpmnt)s "
                        "has generated a request which is not done "
                        "yet. You should either set the request as "
                        "done, remove its maintenance kind or "
                        "delete it first.",
                        kind=plan.maintenance_kind_id.name,
                        eqpmnt=plan.equipment_id.name,
                    )
                )
        return super().unlink()

    _sql_constraints = [
        (
            "equipment_kind_uniq",
            "unique (equipment_id, maintenance_kind_id)",
            "You cannot define multiple times the same maintenance kind on an "
            "equipment maintenance plan.",
        )
    ]

    def button_manual_request_generation(self):
        """Call the same method that the cron for generating manually the maintenance
        requests."""
        for plan in self:
            equipment = plan.equipment_id
            equipment._create_new_request(plan)

    def _get_maintenance_equipments(self):
        self.ensure_one()
        if self.generate_with_domain and not self.equipment_id:
            return self.env["maintenance.equipment"].search(
                safe_eval.safe_eval(
                    self.generate_domain or "[]", self._get_eval_context()
                )
            )
        return [self.equipment_id]
