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

import contextlib

from odoo import _, models, fields, api
from odoo.exceptions import AccessError, UserError
from odoo.tools.misc import limited_field_access_token, verify_limited_field_access_token
from odoo.addons.mail.tools.discuss import Store


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

    thumbnail = fields.Image()
    has_thumbnail = fields.Boolean(compute="_compute_has_thumbnail")

    @api.depends("thumbnail")
    def _compute_has_thumbnail(self):
        for attachment in self.with_context(bin_size=True):
            attachment.has_thumbnail = bool(attachment.thumbnail)

    def _has_attachments_ownership(self, attachment_tokens):
        """ Checks if the current user has ownership of all attachments in the recordset.
            Ownership is defined as either:
            - Having 'write' access to the attachment.
            - Providing a valid, scoped 'attachment_ownership' access token.

            :param list attachment_tokens: A list of access tokens
        """
        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."))

        def is_owned(attachment, token):
            if not attachment.exists():
                return False
            if attachment.sudo(False).has_access("write"):
                return True
            return token and verify_limited_field_access_token(
                attachment, "id", token, scope="attachment_ownership"
            )

        return all(is_owned(att, tok) for att, tok in zip(self, attachment_tokens, strict=True))

    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 _get_store_ownership_fields(self):
        return [Store.Attr("ownership_token", lambda a: a._get_ownership_token())]

    def _to_store_defaults(self, target):
        return [
            "checksum",
            "create_date",
            "file_size",
            "has_thumbnail",
            "mimetype",
            "name",
            Store.Attr("raw_access_token", lambda a: a._get_raw_access_token()),
            "res_name",
            "res_model",
            Store.One("thread", [], as_thread=True),
            Store.Attr("thumbnail_access_token", lambda a: a._get_thumbnail_token()),
            "type",
            "url",
        ]

    def _get_ownership_token(self):
        """ Returns a scoped limited access token that indicates ownership of the attachment when
            using _has_attachments_ownership. If verified by verify_limited_field_access_token,
            accessing the attachment bypasses the ACLs.

            :rtype: str
        """
        self.ensure_one()
        return limited_field_access_token(self, field_name="id", scope="attachment_ownership")

    def _get_thumbnail_token(self):
        self.ensure_one()
        return limited_field_access_token(self, "thumbnail", scope="binary")
