/* @odoo-module */

import { serializeDateTime } from "@web/core/l10n/dates";
import { patch } from "@web/core/utils/patch";
import { MockServer } from "@web/../tests/helpers/mock_server";

const { DateTime } = luxon;

patch(MockServer.prototype, {
    /**
     * @override
     */
    async performRPC(route, args) {
        if (route === "/mail/attachment/upload") {
            const ufile = args.body.get("ufile");
            const is_pending = args.body.get("is_pending") === "true";
            const model = is_pending ? "mail.compose.message" : args.body.get("thread_model");
            const id = is_pending ? 0 : parseInt(args.body.get("thread_id"));
            const attachmentId = this.mockCreate("ir.attachment", {
                // datas,
                mimetype: ufile.type,
                name: ufile.name,
                res_id: id,
                res_model: model,
            });
            if (args.body.get("voice")) {
                this.mockCreate("discuss.voice.metadata", { attachment_id: attachmentId });
            }
            return this._mockIrAttachment_attachmentFormat([attachmentId])[0];
        }
        return super.performRPC(...arguments);
    },
    /**
     * @override
     */
    async _performRPC(route, args) {
        if (route === "/mail/attachment/delete") {
            const { attachment_id } = args;
            return this._mockRouteMailAttachmentRemove(attachment_id);
        }
        if (route === "/discuss/channel/messages") {
            const { search_term, channel_id, after, around, before, limit } = args;
            return this._mockRouteDiscussChannelMessages(
                channel_id,
                search_term,
                before,
                after,
                around,
                limit
            );
        }
        if (route === "/discuss/channel/mute") {
            const { channel_id, minutes } = args;
            return this._mockRouteDiscussChannelMute(channel_id, minutes);
        }
        if (route === "/discuss/channel/pinned_messages") {
            const { channel_id } = args;
            return this._mockRouteDiscussChannelPins(channel_id);
        }
        if (route === "/discuss/channel/notify_typing") {
            const id = args.channel_id;
            const is_typing = args.is_typing;
            return this._mockRouteDiscussChannelNotifyTyping(id, is_typing);
        }
        if (new RegExp("/discuss/channel/\\d+/partner/\\d+/avatar_128").test(route)) {
            return;
        }
        if (route === "/discuss/channel/ping") {
            return;
        }
        if (route === "/discuss/channel/members") {
            const { channel_id, known_member_ids } = args;
            return this._mockDiscussChannelloadOlderMembers([channel_id], known_member_ids);
        }
        if (route === "/mail/history/messages") {
            const { search_term, after, before, limit } = args;
            return this._mockRouteMailMessageHistory(search_term, after, before, limit);
        }
        if (route === "/mail/init_messaging") {
            return this._mockRouteMailInitMessaging();
        }
        if (route === "/mail/inbox/messages") {
            const { search_term, after, around, before, limit } = args;
            return this._mockRouteMailMessageInbox(search_term, after, before, around, limit);
        }
        if (route === "/mail/link_preview") {
            return this._mockRouteMailLinkPreview(args.message_id, args.clear);
        }
        if (route === "/mail/link_preview/delete") {
            const linkPreviews = this.pyEnv["mail.link.preview"].searchRead([
                ["id", "in", args.link_preview_ids],
            ]);
            for (const linkPreview of linkPreviews) {
                this.pyEnv["bus.bus"]._sendone(
                    this._mockMailMessage__busNotificationTarget(linkPreview.message_id[0]),
                    "mail.link.preview/delete",
                    {
                        id: linkPreview.id,
                        message_id: linkPreview.message_id[0],
                    }
                );
            }
            return args;
        }
        if (route === "/mail/load_message_failures") {
            return this._mockRouteMailLoadMessageFailures();
        }
        if (route === "/mail/message/post") {
            const finalData = {};
            for (const allowedField of [
                "attachment_ids",
                "body",
                "message_type",
                "partner_ids",
                "subtype_xmlid",
                "parent_id",
                "partner_emails",
                "partner_additional_values",
            ]) {
                if (args.post_data[allowedField] !== undefined) {
                    finalData[allowedField] = args.post_data[allowedField];
                }
            }
            if (args.thread_model === "discuss.channel") {
                return this._mockDiscussChannelMessagePost(args.thread_id, finalData, args.context);
            }
            return this._mockMailThreadMessagePost(
                args.thread_model,
                [args.thread_id],
                finalData,
                args.context
            );
        }
        if (route === "/mail/message/reaction") {
            return this._mockRouteMailMessageReaction(args);
        }
        if (route === "/mail/message/update_content") {
            this.pyEnv["mail.message"].write([args.message_id], {
                body: args.body,
                attachment_ids: args.attachment_ids,
            });
            this.pyEnv["bus.bus"]._sendone(
                this._mockMailMessage__busNotificationTarget(args.message_id),
                "mail.record/insert",
                {
                    Message: {
                        id: args.message_id,
                        body: args.body,
                        attachments: this._mockIrAttachment_attachmentFormat(args.attachment_ids),
                    },
                }
            );
            return this._mockMailMessageMessageFormat([args.message_id])[0];
        }
        if (route === "/mail/partner/from_email") {
            return this._mockRouteMailPartnerFromEmail(args.emails, args.additional_values);
        }
        if (route === "/mail/read_subscription_data") {
            const follower_id = args.follower_id;
            return this._mockRouteMailReadSubscriptionData(follower_id);
        }
        if (route === "/mail/rtc/channel/join_call") {
            return this._mockRouteMailRtcChannelJoinCall(
                args.channel_id,
                args.check_rtc_session_ids
            );
        }
        if (route === "/mail/rtc/channel/leave_call") {
            return this._mockRouteMailRtcChannelLeaveCall(args.channel_id);
        }
        if (route === "/mail/rtc/session/update_and_broadcast") {
            return this._mockRouteMailRtcSessionUpdateAndBroadcast(args.session_id, args.values);
        }
        if (route === "/mail/starred/messages") {
            const { search_term, after, before, limit } = args;
            return this._mockRouteMailMessageStarredMessages(search_term, after, before, limit);
        }
        if (route === "/mail/thread/data") {
            return this._mockRouteMailThreadData(
                args.thread_model,
                args.thread_id,
                args.request_list
            );
        }
        if (route === "/mail/thread/messages") {
            const { search_term, after, around, before, limit, thread_model, thread_id } = args;
            return this._mockRouteMailThreadFetchMessages(
                thread_model,
                thread_id,
                search_term,
                before,
                after,
                around,
                limit
            );
        }
        if (route === "/discuss/gif/favorites") {
            return [[]];
        }
        return super._performRPC(route, args);
    },
    /**
     * Simulates the `/discuss/channel/pinned_messages` route.
     */
    _mockRouteDiscussChannelPins(channel_id) {
        const messageIds = this.pyEnv["mail.message"].search([
            ["model", "=", "discuss.channel"],
            ["res_id", "=", channel_id],
            ["pinned_at", "!=", false],
        ]);
        return this._mockMailMessageMessageFormat(messageIds);
    },
    /**
     * Simulates the `/mail/init_messaging` route.
     *
     * @private
     * @returns {Object}
     */
    _mockRouteMailInitMessaging() {
        if (this._mockMailGuest__getGuestFromContext() && this.pyEnv.currentUser?._is_public()) {
            return this._mockMailGuest__initMessaging();
        }
        return this._mockResUsers_InitMessaging([this.pyEnv.currentUserId]);
    },
    /**
     * Simulates the `/mail/attachment/delete` route.
     *
     * @private
     * @param {integer} attachment_id
     */
    async _mockRouteMailAttachmentRemove(attachment_id) {
        this.pyEnv["bus.bus"]._sendone(this.pyEnv.currentPartner, "ir.attachment/delete", {
            id: attachment_id,
        });
        return this.pyEnv["ir.attachment"].unlink([attachment_id]);
    },
    /**
     * Simulates the `/discuss/channel/messages` route.
     *
     * @private
     * @param {integer} channel_id
     * @param {integer} limit
     * @param {integer} before
     * @param {integer} after
     * @param {integer} around
     * @returns {Object} list of messages
     */
    async _mockRouteDiscussChannelMessages(
        channel_id,
        search_term = false,
        before = false,
        after = false,
        around = false,
        limit = 30
    ) {
        const domain = [
            ["res_id", "=", channel_id],
            ["model", "=", "discuss.channel"],
            ["message_type", "!=", "user_notification"],
        ];
        const res = this._mockMailMessage_MessageFetch(
            domain,
            search_term,
            before,
            after,
            around,
            limit
        );
        if (!around) {
            this._mockMailMessageSetMessageDone(res.messages.map((message) => message.id));
        }
        return {
            ...res,
            messages: this._mockMailMessageMessageFormat(res.messages.map((message) => message.id)),
        };
    },
    /**
     * Simulates the `/discuss/channel/mute` route.
     *
     * @private
     * @param {integer} channel_id
     * @param {integer} minutes
     */
    _mockRouteDiscussChannelMute(channel_id, minutes) {
        const member = this._mockDiscussChannelMember__getAsSudoFromContext(channel_id);
        let mute_until_dt;
        if (minutes === -1) {
            mute_until_dt = serializeDateTime(DateTime.fromISO("9999-12-31T23:59:59"));
        } else if (minutes) {
            mute_until_dt = serializeDateTime(DateTime.now().plus({ minutes }));
        } else {
            mute_until_dt = false;
        }
        this.pyEnv["discuss.channel.member"].write([member.id], { mute_until_dt });
        const channel_data = {
            id: member.channel_id[0],
            model: "discuss.channel",
            mute_until_dt,
        };
        this.pyEnv["bus.bus"]._sendone(this.pyEnv.currentPartner, "mail.record/insert", {
            Thread: channel_data,
        });
        return "dummy";
    },
    /**
     * Simulates the `/discuss/channel/notify_typing` route.
     *
     * @private
     * @param {integer} channel_id
     * @param {integer} limit
     * @param {Object} [context={}]
     */
    async _mockRouteDiscussChannelNotifyTyping(channel_id, is_typing) {
        const memberOfCurrentUser =
            this._mockDiscussChannelMember__getAsSudoFromContext(channel_id);
        if (!memberOfCurrentUser) {
            return;
        }
        this._mockDiscussChannelMember_NotifyTyping([memberOfCurrentUser.id], is_typing);
    },
    /**
     * Simulates the `/mail/load_message_failures` route.
     *
     * @private
     * @returns {Object[]}
     */
    _mockRouteMailLoadMessageFailures() {
        return this._mockResPartner_MessageFetchFailed(this.pyEnv.currentPartnerId);
    },
    /**
     * Simulates `/mail/link_preview` route.
     *
     * @private
     * @param {integer} message_id
     * @returns {Object}
     */
    _mockRouteMailLinkPreview(message_id, clear = false) {
        const linkPreviews = [];
        const [message] = this.pyEnv["mail.message"].searchRead([["id", "=", message_id]]);
        if (message.body.includes("https://make-link-preview.com")) {
            if (clear) {
                const [linkPreview] = this.pyEnv["mail.link.preview"].searchRead([
                    ["message_id", "=", message_id],
                ]);
                this.pyEnv["bus.bus"]._sendone(
                    this._mockMailMessage__busNotificationTarget(linkPreview.message_id),
                    "mail.link.preview/delete",
                    {
                        id: linkPreview.id,
                        message_id: linkPreview.message_id,
                    }
                );
            }

            const linkPreviewId = this.pyEnv["mail.link.preview"].create({
                message_id: message.id,
                og_description: "test description",
                og_title: "Article title",
                og_type: "article",
                source_url: "https://make-link-preview.com",
            });
            const [linkPreview] = this.pyEnv["mail.link.preview"].searchRead([
                ["id", "=", linkPreviewId],
            ]);
            linkPreviews.push(this._mockMailLinkPreviewFormat(linkPreview));
            this.pyEnv["bus.bus"]._sendone(
                this._mockMailMessage__busNotificationTarget(message_id),
                "mail.record/insert",
                { LinkPreview: linkPreviews }
            );
        }
    },
    /**
     * Simulates `/mail/message/reaction` route.
     */
    _mockRouteMailMessageReaction({ action, content, message_id }) {
        return this._mockMailMessage_messageReaction(message_id, content, action);
    },
    /**
     * Simulates the `/mail/history/messages` route.
     *
     * @private
     * @returns {Object}
     */
    _mockRouteMailMessageHistory(search_term = false, after = false, before = false, limit = 30) {
        const domain = [["needaction", "=", false]];
        const res = this._mockMailMessage_MessageFetch(
            domain,
            search_term,
            before,
            after,
            false,
            limit
        );
        const messagesWithNotification = res.messages.filter((message) => {
            const notifs = this.pyEnv["mail.notification"].searchRead([
                ["mail_message_id", "=", message.id],
                ["is_read", "=", true],
                ["res_partner_id", "=", this.pyEnv.currentPartnerId],
            ]);
            return notifs.length > 0;
        });

        return {
            ...res,
            messages: this._mockMailMessageMessageFormat(
                messagesWithNotification.map((message) => message.id)
            ),
        };
    },
    /**
     * Simulates the `/mail/inbox/messages` route.
     *
     * @private
     * @returns {Object}
     */
    _mockRouteMailMessageInbox(
        search_term = false,
        after = false,
        before = false,
        around = false,
        limit = 30
    ) {
        const domain = [["needaction", "=", true]];
        const res = this._mockMailMessage_MessageFetch(
            domain,
            search_term,
            before,
            after,
            around,
            limit
        );
        return {
            ...res,
            messages: this._mockMailMessageFormatPersonalize(
                res.messages.map((message) => message.id)
            ),
        };
    },
    /**
     * Simulates the `/mail/starred/messages` route.
     *
     * @private
     * @returns {Object}
     */
    _mockRouteMailMessageStarredMessages(
        search_term = false,
        after = false,
        before = false,
        limit = 30
    ) {
        const domain = [["starred_partner_ids", "in", [this.pyEnv.currentPartnerId]]];
        const res = this._mockMailMessage_MessageFetch(
            domain,
            search_term,
            before,
            after,
            false,
            limit
        );
        return {
            ...res,
            messages: this._mockMailMessageMessageFormat(res.messages.map((message) => message.id)),
        };
    },
    /**
     * Simulates the `/mail/partner/from_email` route.
     *
     * @private
     * @param {string[]} emails
     * @returns {Object[]} list of partner data
     */
    _mockRouteMailPartnerFromEmail(emails, additional_values = {}) {
        const partners = emails.map(
            (email) => this.pyEnv["res.partner"].search([["email", "=", email]])[0]
        );
        for (const index in partners) {
            if (!partners[index]) {
                const email = emails[index];
                partners[index] = this.pyEnv["res.partner"].create({
                    email,
                    name: email,
                    ...(additional_values[email] || {}),
                });
            }
        }
        return partners.map((partner_id) => {
            const partner = this.getRecords("res.partner", [["id", "=", partner_id]])[0];
            return { id: partner_id, name: partner.name, email: partner.email };
        });
    },
    /**
     * Simulates the `/mail/read_subscription_data` route.
     *
     * @private
     * @param {integer} follower_id
     * @returns {Object[]} list of followed subtypes
     */
    async _mockRouteMailReadSubscriptionData(follower_id) {
        const follower = this.getRecords("mail.followers", [["id", "=", follower_id]])[0];
        const subtypes = this.getRecords("mail.message.subtype", [
            "&",
            ["hidden", "=", false],
            "|",
            ["res_model", "=", follower.res_model],
            ["res_model", "=", false],
        ]);
        const subtypes_list = subtypes.map((subtype) => {
            const parent = this.getRecords("mail.message.subtype", [
                ["id", "=", subtype.parent_id],
            ])[0];
            return {
                default: subtype.default,
                followed: follower.subtype_ids.includes(subtype.id),
                id: subtype.id,
                internal: subtype.internal,
                name: subtype.name,
                parent_model: parent ? parent.res_model : false,
                res_model: subtype.res_model,
                sequence: subtype.sequence,
            };
        });
        // NOTE: server is also doing a sort here, not reproduced for simplicity
        return subtypes_list;
    },
    /**
     * Simulates the `/mail/rtc/channel/join_call` route.
     *
     * @private
     * @param {integer} channel_id
     * @returns {integer[]} [check_rtc_session_ids]
     */
    async _mockRouteMailRtcChannelJoinCall(channel_id, check_rtc_session_ids = []) {
        const memberOfCurrentUser =
            this._mockDiscussChannelMember__getAsSudoFromContext(channel_id);
        const sessionId = this.pyEnv["discuss.channel.rtc.session"].create({
            channel_member_id: memberOfCurrentUser.id,
            channel_id, // on the server, this is a related field from channel_member_id and not explicitly set
            guest_id: memberOfCurrentUser.guest_id[0],
            partner_id: memberOfCurrentUser.partner_id[0],
        });
        const channelMembers = this.getRecords("discuss.channel.member", [
            ["channel_id", "=", channel_id],
        ]);
        const rtcSessions = this.getRecords("discuss.channel.rtc.session", [
            ["channel_member_id", "in", channelMembers.map((channelMember) => channelMember.id)],
        ]);
        return {
            iceServers: false,
            rtcSessions: [
                [
                    "ADD",
                    rtcSessions.map((rtcSession) =>
                        this._mockDiscussChannelRtcSession_DiscussChannelRtcSessionFormat(
                            rtcSession.id
                        )
                    ),
                ],
            ],
            sessionId: sessionId,
        };
    },
    /**
     * Simulates the `/mail/rtc/channel/leave_call` route.
     *
     * @private
     * @param {integer} channelId
     */
    async _mockRouteMailRtcChannelLeaveCall(channel_id) {
        const channelMembers = this.getRecords("discuss.channel.member", [
            ["channel_id", "=", channel_id],
        ]);
        const rtcSessions = this.getRecords("discuss.channel.rtc.session", [
            ["channel_member_id", "in", channelMembers.map((channelMember) => channelMember.id)],
        ]);
        const notifications = [];
        const channelInfo =
            this._mockDiscussChannelRtcSession_DiscussChannelRtcSessionFormatByChannel(
                rtcSessions.map((rtcSession) => rtcSession.id)
            );
        for (const [channelId, sessionsData] of Object.entries(channelInfo)) {
            const channel = this.pyEnv["discuss.channel"].searchRead([
                ["id", "=", parseInt(channelId)],
            ])[0];
            const notificationRtcSessions = sessionsData.map((sessionsDataPoint) => {
                return { id: sessionsDataPoint.id };
            });
            notifications.push([
                channel,
                "discuss.channel/rtc_sessions_update",
                {
                    id: Number(channelId), // JS object keys are strings, but the type from the server is number
                    rtcSessions: [["DELETE", notificationRtcSessions]],
                },
            ]);
        }
        for (const rtcSession of rtcSessions) {
            const target = rtcSession.guest_id
                ? this.pyEnv["mail.guest"].searchRead([["id", "=", rtcSession.guest_id]])[0]
                : this.pyEnv["res.partner"].searchRead([["id", "=", rtcSession.partner_id]])[0];
            notifications.push([
                target,
                "discuss.channel.rtc.session/ended",
                { sessionId: rtcSession.id },
            ]);
        }
        this.pyEnv["bus.bus"]._sendmany(notifications);
    },
    /**
     * Simulates the `/mail/rtc/session/update_and_broadcast` route.
     *
     * @param {number} session_id
     * @param {object} values
     */
    async _mockRouteMailRtcSessionUpdateAndBroadcast(session_id, values) {
        const [session] = this.pyEnv["discuss.channel.rtc.session"].searchRead([
            ["id", "=", session_id],
        ]);
        const [currentChannelMember] = this.pyEnv["discuss.channel.member"].searchRead([
            ["id", "=", session.channel_member_id[0]],
        ]);
        if (session && currentChannelMember.partner_id[0] === this.pyEnv.currentPartnerId) {
            this._mockDiscussChannelRtcSession__updateAndBroadcast(session.id, values);
        }
    },
    /**
     * Simulates the `/mail/thread/data` route.
     *
     * @param {string} thread_model
     * @param {integer} thread_id
     * @param {string[]} request_list
     * @returns {Object}
     */
    async _mockRouteMailThreadData(thread_model, thread_id, request_list) {
        const res = {
            hasWriteAccess: true, // mimic user with write access by default
            hasReadAccess: true,
        };
        const thread = this.pyEnv[thread_model].searchRead([["id", "=", thread_id]])[0];
        if (!thread) {
            res["hasReadAccess"] = false;
            return res;
        }
        res["canPostOnReadonly"] = thread_model === "discuss.channel"; // model that have attr _mail_post_access='read'
        if (request_list.includes("activities")) {
            const activities = this.pyEnv["mail.activity"].searchRead([
                ["id", "in", thread.activity_ids || []],
            ]);
            res["activities"] = this._mockMailActivityActivityFormat(
                activities.map((activity) => activity.id)
            );
        }
        if (request_list.includes("attachments")) {
            const attachments = this.pyEnv["ir.attachment"].searchRead([
                ["res_id", "=", thread.id],
                ["res_model", "=", thread_model],
            ]); // order not done for simplicity
            res["attachments"] = this._mockIrAttachment_attachmentFormat(
                attachments.map((attachment) => attachment.id)
            );
            // Specific implementation of mail.thread.main.attachment
            if (this.models[thread_model].fields["message_main_attachment_id"]) {
                res["mainAttachment"] = thread.message_main_attachment_id
                    ? { id: thread.message_main_attachment_id[0] }
                    : false;
            }
        }
        if (request_list.includes("followers")) {
            const domain = [
                ["res_id", "=", thread.id],
                ["res_model", "=", thread_model],
            ];
            res["followersCount"] = (thread.message_follower_ids || []).length;
            const selfFollower = this.pyEnv["mail.followers"].searchRead(
                domain.concat([["partner_id", "=", this.pyEnv.currentPartnerId]])
            )[0];
            res["selfFollower"] = selfFollower
                ? this._mockMailFollowers_FormatForChatter(selfFollower.id)[0]
                : false;
            res["followers"] = this._mockMailThreadMessageGetFollowers(thread_model, [thread_id]);
            res["recipientsCount"] = (thread.message_follower_ids || []).length - 1;
            res["recipients"] = this._mockMailThreadMessageGetFollowers(
                thread_model,
                [thread_id],
                undefined,
                100,
                { filter_recipients: true }
            );
        }
        if (request_list.includes("suggestedRecipients")) {
            res["suggestedRecipients"] = this._mockMailThread_MessageGetSuggestedRecipients(
                thread_model,
                [thread.id]
            )[thread_id];
        }
        return res;
    },
    /**
     * Simulates the `/mail/thread/messages` route.
     *
     * @private
     * @param {string} res_model
     * @param {integer} res_id
     * @param {integer} before
     * @param {integer} after
     * @param {integer} limit
     * @returns {Object[]} list of messages
     */
    async _mockRouteMailThreadFetchMessages(
        res_model,
        res_id,
        search_term = false,
        before = false,
        after = false,
        around = false,
        limit = 30
    ) {
        const domain = [
            ["res_id", "=", res_id],
            ["model", "=", res_model],
            ["message_type", "!=", "user_notification"],
        ];
        const res = this._mockMailMessage_MessageFetch(
            domain,
            search_term,
            before,
            after,
            around,
            limit
        );
        this._mockMailMessageSetMessageDone(res.messages.map((message) => message.id));
        return {
            ...res,
            messages: this._mockMailMessageMessageFormat(res.messages.map((message) => message.id)),
        };
    },
});
