# Part of Odoo. See LICENSE file for full copyright and licensing details.

import contextlib

from odoo import _, models, SUPERUSER_ID
from odoo.exceptions import AccessError, MissingError, UserError
from odoo.tools import consteq
from odoo.addons.mail.tools.discuss import Store


class IrAttachment(models.Model):
    _inherit = 'ir.attachment'

    def _check_attachments_access(self, attachment_tokens):
        """This method relies on access rules/rights and therefore it should not be called from a sudo env."""
        self = self.sudo(False)
        attachment_tokens = attachment_tokens or ([None] * len(self))
        if len(attachment_tokens) != len(self):
            raise UserError(_("An access token must be provided for each attachment."))
        for attachment, access_token in zip(self, attachment_tokens):
            try:
                attachment_sudo = attachment.with_user(SUPERUSER_ID).exists()
                if not attachment_sudo:
                    raise MissingError(_("The attachment %s does not exist.", attachment.id))
                try:
                    attachment.check('write')
                except AccessError:
                    if not access_token or not attachment_sudo.access_token or not consteq(attachment_sudo.access_token, access_token):
                        message_sudo = self.env['mail.message'].sudo().search([('attachment_ids', 'in', attachment_sudo.ids)], limit=1)
                        if not message_sudo or not message_sudo.is_current_user_or_guest_author:
                            raise
            except (AccessError, MissingError):
                raise UserError(_("The attachment %s does not exist or you do not have the rights to access it.", attachment.id))

    def _post_add_create(self, **kwargs):
        """ Overrides behaviour when the attachment is created through the controller
        """
        super()._post_add_create(**kwargs)
        self.register_as_main_attachment(force=False)

    def register_as_main_attachment(self, force=True):
        """ Registers this attachment as the main one of the model it is
        attached to.

        :param bool force: if set, the method always updates the existing main attachment
            otherwise it only sets the main attachment if there is none.
        """
        todo = self.filtered(lambda a: a.res_model and a.res_id)
        if not todo:
            return

        for model, attachments in todo.grouped("res_model").items():
            related_records = self.env[model].browse(attachments.mapped("res_id"))
            if not hasattr(related_records, '_message_set_main_attachment_id'):
                return

            # this action is generic; if user cannot update record do not crash
            # just skip update
            for related_record, attachment in zip(related_records, attachments):
                with contextlib.suppress(AccessError):
                    related_record._message_set_main_attachment_id(attachment, force=force)

    def _delete_and_notify(self, message=None):
        if message:
            # sudo: mail.message - safe write just updating the date, because guests don't have the rights
            message.sudo().write({})  # to make sure write_date on the message is updated
        for attachment in self:
            attachment._bus_send(
                "ir.attachment/delete",
                {
                    "id": attachment.id,
                    "message": (
                        {"id": message.id, "write_date": message.write_date} if message else None
                    ),
                },
            )
        self.unlink()

    def _to_store(self, store: Store, /, *, fields=None, extra_fields=None):
        if fields is None:
            fields = [
                "checksum",
                "create_date",
                "filename",
                "mimetype",
                "name",
                "res_name",
                "size",
                "thread",
                "type",
                "url",
            ]
        if extra_fields:
            fields.extend(extra_fields)
        for attachment in self:
            data = attachment._read_format(
                [field for field in fields if field not in ["filename", "size", "thread"]],
                load=False,
            )[0]
            if "filename" in fields:
                data["filename"] = attachment.name
            if "size" in fields:
                data["size"] = attachment.file_size
            if "thread" in fields:
                data["thread"] = (
                    Store.one(
                        self.env[attachment.res_model].browse(attachment.res_id),
                        as_thread=True,
                        only_id=True,
                    )
                    if attachment.res_model != "mail.compose.message" and attachment.res_id
                    else False
                )
            store.add(attachment, data)
