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

from odoo import api, fields, models
from odoo.fields import Domain
from odoo.tools import email_normalize, single_email_re, SQL
from odoo.addons.mail.tools.discuss import Store
from odoo.exceptions import AccessError


class ResPartner(models.Model):
    _inherit = "res.partner"

    channel_ids = fields.Many2many(
        "discuss.channel",
        "discuss_channel_member",
        "partner_id",
        "channel_id",
        string="Channels",
        copy=False,
    )
    channel_member_ids = fields.One2many("discuss.channel.member", "partner_id")
    is_in_call = fields.Boolean(compute="_compute_is_in_call", groups="base.group_system")
    rtc_session_ids = fields.One2many("discuss.channel.rtc.session", "partner_id")

    @api.depends("rtc_session_ids")
    def _compute_is_in_call(self):
        for partner in self:
            partner.is_in_call = bool(partner.rtc_session_ids)

    @api.readonly
    @api.model
    def search_for_channel_invite(self, search_term, channel_id=None, limit=30):
        """Returns partners matching search_term that can be invited to a channel.

        - If `channel_id` is specified, only partners that can actually be invited to the channel
          are returned (not already members, and in accordance to the channel configuration).

        - If no matching partners are found and the search term is a valid email address,
          then the method may return `selectable_email` as a fallback direct email invite, provided that
          the channel allows invites by email.

        """
        store = Store()
        channel_invites = self._search_for_channel_invite(store, search_term, channel_id, limit)
        selectable_email = None
        email_already_sent = None
        if channel_invites["count"] == 0 and single_email_re.match(search_term):
            email = email_normalize(search_term)
            channel = self.env["discuss.channel"].search_fetch([("id", "=", int(channel_id))])
            member_domain = Domain("channel_id", "=", channel.id)
            member_domain &= Domain("guest_id.email", "=", email) | Domain(
                "partner_id.email", "=", email
            )
            if channel._allow_invite_by_email() and not self.env[
                "discuss.channel.member"
            ].search_count(member_domain):
                selectable_email = email
                # sudo - mail.mail: checking mail records to determine if an email was already sent is acceptable.
                email_already_sent = (
                    self.env["mail.mail"]
                    .sudo()
                    .search_count(
                        [
                            ("email_to", "=", email),
                            ("model", "=", "discuss.channel"),
                            ("res_id", "=", channel.id),
                        ]
                    )
                    > 0
                )

        return {
            **channel_invites,
            "email_already_sent": email_already_sent,
            "selectable_email": selectable_email,
            "store_data": store.get_result(),
        }

    @api.readonly
    @api.model
    def _search_for_channel_invite(self, store: Store, search_term, channel_id=None, limit=30):
        domain = Domain.AND(
            [
                Domain("name", "ilike", search_term) | Domain("email", "ilike", search_term),
                [('id', '!=', self.env.user.partner_id.id)],
                [("active", "=", True)],
                [("user_ids", "!=", False)],
                [("user_ids.active", "=", True)],
                [("user_ids.share", "=", False)],
            ]
        )
        channel = self.env["discuss.channel"]
        if channel_id:
            channel = self.env["discuss.channel"].search([("id", "=", int(channel_id))])
            domain &= Domain("channel_ids", "not in", channel.id)
            if channel.group_public_id:
                domain &= Domain("user_ids.all_group_ids", "in", channel.group_public_id.id)
        query = self._search(domain, limit=limit)
        # bypass lack of support for case insensitive order in search()
        query.order = SQL('LOWER(%s), "res_partner"."id"', self._field_to_sql(self._table, "name"))
        selectable_partners = self.env["res.partner"].browse(query)
        selectable_partners._search_for_channel_invite_to_store(store, channel)
        return {
            "count": self.env["res.partner"].search_count(domain),
            "partner_ids": selectable_partners.ids,
        }

    def _search_for_channel_invite_to_store(self, store: Store, channel):
        store.add(self)

    @api.readonly
    @api.model
    def get_mention_suggestions_from_channel(self, channel_id, search, limit=8):
        """Return 'limit'-first partners' such that the name or email matches a 'search' string.
        Prioritize partners that are also (internal) users, and then extend the research to all partners.
        Only members of the given channel are returned.
        The return format is a list of partner data (as per returned by `_to_store()`).
        """
        channel = self.env["discuss.channel"].search([("id", "=", channel_id)])
        if not channel:
            return []
        domain = Domain([
            self._get_mention_suggestions_domain(search),
            ("channel_ids", "in", (channel.parent_channel_id | channel).ids)
        ])
        extra_domain = Domain([
            ('user_ids', '!=', False),
            ('user_ids.active', '=', True),
            ('partner_share', '=', False),
        ])
        allowed_group = (channel.parent_channel_id or channel).group_public_id
        if allowed_group:
            extra_domain &= Domain("user_ids.all_group_ids", "in", allowed_group.id)
        partners = self._search_mention_suggestions(domain, limit, extra_domain)
        members_domain = [
            ("channel_id", "in", (channel.parent_channel_id | channel).ids),
            ("partner_id", "in", partners.ids)
        ]
        members = self.env["discuss.channel.member"].search(members_domain)
        member_fields = [
            Store.One("channel_id", [], as_thread=True),
            *self.env["discuss.channel.member"]._to_store_persona([]),
        ]
        store = (
            Store()
            .add(members, member_fields)
            .add(partners, extra_fields=partners._get_store_mention_fields())
        )
        store.add(channel, "group_public_id")
        if allowed_group:
            for p in partners:
                store.add(p, {"group_ids": [("ADD", (allowed_group & p.user_ids.all_group_ids).ids)]})
        try:
            roles = self.env["res.role"].search([("name", "ilike", search)], limit=8)
            store.add(roles, "name")
        except AccessError:
            pass
        return store.get_result()
