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

from urllib.parse import urlsplit
from zoneinfo import ZoneInfo

from werkzeug.exceptions import NotFound

from odoo import _, http
from odoo.http import request
from odoo.http.stream import Stream, content_disposition
from odoo.tools import format_list

from odoo.addons.mail.tools.discuss import Store, add_guest_to_context


class LivechatController(http.Controller):

    # Note: the `cors` attribute on many routes is meant to allow the livechat
    # to be embedded in an external website.

    @http.route('/im_livechat/external_lib.<any(css,js):ext>', type='http', auth='public', cors='*')
    def external_lib(self, ext, **kwargs):
        """ Preserve compatibility with legacy livechat imports. Only
        serves javascript since the css will be fetched by the shadow
        DOM of the livechat to avoid conflicts.
        """
        if ext == 'css':
            raise request.not_found()
        return self.assets_embed(ext, **kwargs)

    def _is_cors_request(self):
        headers = request.httprequest.headers
        origin_url = urlsplit(headers.get("referer"))
        return (
            origin_url.netloc != headers.get("host")
            or origin_url.scheme != request.httprequest.scheme
        )

    @http.route('/im_livechat/assets_embed.<any(css, js):ext>', type='http', auth='public', cors='*')
    def assets_embed(self, ext, **kwargs):
        # If the request comes from a different origin, we must provide the CORS
        # assets to enable the redirection of routes to the CORS controller.
        bundle = "im_livechat.assets_embed_cors" if self._is_cors_request() else "im_livechat.assets_embed_external"
        asset = request.env["ir.qweb"]._get_asset_bundle(bundle)
        if ext not in ('css', 'js'):
            raise request.not_found()
        stream = request.env['ir.binary']._get_stream_from(getattr(asset, ext)())
        return stream.get_response()

    @http.route('/im_livechat/font-awesome', type='http', auth='none', cors="*")
    def fontawesome(self, **kwargs):
        return Stream.from_path('web/static/src/libs/fontawesome/fonts/fontawesome-webfont.woff2').get_response()

    @http.route('/im_livechat/odoo_ui_icons', type='http', auth='none', cors="*")
    def odoo_ui_icons(self, **kwargs):
        return Stream.from_path('web/static/lib/odoo_ui_icons/fonts/odoo_ui_icons.woff2').get_response()

    @http.route('/im_livechat/emoji_bundle', type='http', auth='public', cors='*')
    def get_emoji_bundle(self):
        bundle = 'web.assets_emoji'
        asset = request.env["ir.qweb"]._get_asset_bundle(bundle)
        stream = request.env['ir.binary']._get_stream_from(asset.js())
        return stream.get_response()

    @http.route('/im_livechat/support/<int:channel_id>', type='http', auth='public')
    def support_page(self, channel_id, **kwargs):
        channel = request.env['im_livechat.channel'].sudo().browse(channel_id)
        return request.render('im_livechat.support_page', {'channel': channel})

    @http.route('/im_livechat/loader/<int:channel_id>', type='http', auth='public')
    def loader(self, channel_id, **kwargs):
        username = kwargs.get("username", _("Visitor"))
        channel = request.env['im_livechat.channel'].sudo().browse(channel_id)
        info = channel.get_livechat_info(username=username)
        return request.render('im_livechat.loader', {'info': info}, headers=[('Content-Type', 'application/javascript')])

    def _process_extra_channel_params(self, **kwargs):
        # non_persisted_channel_params, persisted_channel_params
        return {}, {}

    def _get_guest_name(self):
        return _("Visitor")

    @http.route('/im_livechat/get_session', methods=["POST"], type="jsonrpc", auth='public')
    @add_guest_to_context
    def get_session(self, channel_id, previous_operator_id=None, chatbot_script_id=None, persisted=True, **kwargs):
        channel = request.env["discuss.channel"]
        country = request.env["res.country"]
        guest = request.env["mail.guest"]
        store = Store()
        livechat_channel = (
            request.env["im_livechat.channel"]
            .with_context(lang=False)
            .sudo()
            .search([("id", "=", channel_id)])
        )
        if not livechat_channel:
            raise NotFound()
        if not request.env.user._is_public():
            country = request.env.user.country_id
        elif request.geoip.country_code:
            country = request.env["res.country"].search(
                [("code", "=", request.geoip.country_code)], limit=1
            )
        operator_info = livechat_channel._get_operator_info(
            previous_operator_id=previous_operator_id,
            chatbot_script_id=chatbot_script_id,
            country_id=country.id,
            lang=request.cookies.get("frontend_lang"),
            **kwargs
        )
        if not operator_info['operator_partner']:
            return False

        chatbot_script = operator_info['chatbot_script']
        is_chatbot_script = operator_info['operator_model'] == 'chatbot.script'
        non_persisted_channel_params, persisted_channel_params = self._process_extra_channel_params(**kwargs)

        if not persisted:
            channel_id = -1  # only one temporary thread at a time, id does not matter.
            temp_member_history_id = -1
            chatbot_data = None
            if is_chatbot_script:
                welcome_steps = chatbot_script._get_welcome_steps()
                chatbot_data = {
                    "script": chatbot_script.id,
                    "steps": welcome_steps.mapped(lambda s: {"scriptStep": s.id}),
                }
                store.add(chatbot_script, "_store_script_fields")
                store.add(welcome_steps, "_store_script_step_fields")
            store.add_model_values(
                "discuss.channel",
                lambda res: (
                    res.attr("fetchChannelInfoState", "fetched"),
                    res.attr("id", channel_id),
                    res.attr("isLoaded", True),
                    res.attr("livechat_channel_member_history_ids", [temp_member_history_id]),
                    res.attr("scrollUnread", False),
                    res.attr("channel_type", "livechat"),
                    res.attr("chatbot", chatbot_data),
                    res.append(non_persisted_channel_params),
                ),
            )
            store.add_model_values(
                "im_livechat.channel.member.history",
                lambda res: (
                    res.attr("id", temp_member_history_id),
                    res.attr("channel_id", channel_id),
                    res.attr("livechat_member_type", "bot" if is_chatbot_script else "agent"),
                    res.one(
                        "partner_id",
                        "_store_livechat_member_fields",
                        value=operator_info["operator_partner"],
                    ),
                ),
            )
        else:
            if request.env.user._is_public():
                guest = guest.sudo()._get_or_create_guest(
                    guest_name=self._get_guest_name(),
                    country_code=request.geoip.country_code,
                    timezone=request.env["mail.guest"]._get_timezone_from_request(request),
                )
                livechat_channel = livechat_channel.with_context(guest=guest)
                request.update_context(guest=guest)
            channel_vals = livechat_channel._get_livechat_discuss_channel_vals(**operator_info)
            channel_vals.update(**persisted_channel_params)
            lang = request.env["res.lang"].search(
                [("code", "=", request.cookies.get("frontend_lang"))]
            )
            channel_vals.update({"country_id": country.id, "livechat_lang_id": lang.id})
            channel = request.env['discuss.channel'].with_context(
                lang=request.env['chatbot.script']._get_chatbot_language()
            ).sudo().create(channel_vals)
            channel_id = channel.id
            if is_chatbot_script:
                chatbot_script._post_welcome_steps(channel)
            if agents := channel.livechat_agent_partner_ids:
                channel._broadcast(agents.ids)
            if guest:
                store.add_global_values(guest_token=guest.sudo()._format_auth_cookie())
        store.add_global_values(request.env.user.sudo(False)._store_init_global_fields)
        # Make sure not to send "isLoaded" value on the guest bus, otherwise it
        # could be overwritten.
        if channel:
            store.add(
                channel,
                lambda res: (
                    res.from_method("_store_channel_fields"),
                    res.attr("isLoaded", not is_chatbot_script),
                    res.attr("scrollUnread", False),
                ),
            )
        if not request.env.user._is_public():
            store.add(request.env.user.partner_id, ["email"])
        return {"store_data": store.get_result(), "channel_id": channel_id}

    @http.route("/im_livechat/feedback", type="jsonrpc", auth="public")
    @add_guest_to_context
    def feedback(self, channel_id, rate, reason=None, **kwargs):
        if channel := request.env["discuss.channel"].search([("id", "=", channel_id)]):
            return channel._apply_livechat_feedback(rate, reason, **kwargs)
        return False

    @http.route("/im_livechat/history", type="jsonrpc", auth="public")
    @add_guest_to_context
    def history_pages(self, pid, channel_id, page_history=None):
        if channel := request.env["discuss.channel"].search([("id", "=", channel_id)]):
            if pid in channel.sudo().channel_member_ids.partner_id.ids:
                request.env["res.partner"].browse(pid)._bus_send_history_message(channel, page_history)

    @http.route("/im_livechat/email_livechat_transcript", type="jsonrpc", auth="user")
    @add_guest_to_context
    def email_livechat_transcript(self, channel_id, email):
        if not request.env.user._is_internal():
            raise NotFound()
        if channel := request.env["discuss.channel"].search([("id", "=", channel_id)]):
            channel._email_livechat_transcript(email)

    @http.route("/im_livechat/download_transcript/<int:channel_id>", type="http", auth="public")
    @add_guest_to_context
    def download_livechat_transcript(self, channel_id):
        channel = request.env["discuss.channel"].search([("id", "=", channel_id)])
        if not channel:
            raise NotFound()
        user, guest = request.env["res.users"]._get_current_persona()
        tz = ZoneInfo(user.tz or guest.timezone or "UTC")
        pdf, _type = (
            request.env["ir.actions.report"]
            .sudo()
            ._render_qweb_pdf(
                "im_livechat.action_report_livechat_conversation",
                channel.ids,
                data={
                    "company": request.env.company,
                    "tz": tz,
                    "correspondent_names": format_list(
                        request.env,
                        (
                            # sudo - res.partner: visitors can access bots and agents name
                            channel.sudo().livechat_agent_partner_ids
                            or channel.sudo().livechat_bot_partner_ids
                        ).mapped(lambda p: p.user_livechat_username or p.name),
                    ),
                },
            )
        )
        headers = [
            ("Content-Disposition", content_disposition(f"transcript_{channel.id}.pdf", "inline")),
            ("Content-Length", len(pdf)),
            ("Content-Type", "application/pdf"),
        ]
        return request.make_response(pdf, headers=headers)

    @http.route("/im_livechat/visitor_leave_session", type="jsonrpc", auth="public")
    @add_guest_to_context
    def visitor_leave_session(self, channel_id):
        """Called when the livechat visitor leaves the conversation.
        This will clean the chat request and warn the operator that the conversation is over.
        This allows also to re-send a new chat request to the visitor, as while the visitor is
        in conversation with an operator, it's not possible to send the visitor a chat request."""
        if channel := request.env["discuss.channel"].search([("id", "=", channel_id)]):
            channel._close_livechat_session(message=channel._get_visitor_leave_message())
