#
# SPDX-License-Identifier: GPL-3.0-only

from typing import cast

import logging
from pathlib import Path

from gi.repository import Gtk
from nbxmpp.const import Affiliation
from nbxmpp.errors import StanzaError
from nbxmpp.modules.vcard_temp import VCard
from nbxmpp.namespaces import Namespace
from nbxmpp.structs import MucSubject
from nbxmpp.task import Task

from gajim.common import app
from gajim.common.const import AvatarSize
from gajim.common.i18n import _
from gajim.common.modules.contacts import GroupchatContact
from gajim.common.util.jid import validate_jid

from gajim.gtk.avatar_selector import AvatarSelector
from gajim.gtk.builder import get_builder
from gajim.gtk.dialogs import ErrorDialog
from gajim.gtk.filechoosers import AvatarFileChooserButton
from gajim.gtk.util import get_app_window
from gajim.gtk.util import SignalManager
from gajim.gtk.widgets import GajimAppWindow

log = logging.getLogger('gajim.gtk.groupchat_manage')


class GroupchatManage(Gtk.Box, SignalManager):
    def __init__(self,
                 account: str,
                 contact: GroupchatContact,
                 ) -> None:
        Gtk.Box.__init__(self)
        SignalManager.__init__(self)

        self._account = account
        self._client = app.get_client(account)
        self._contact = contact
        self._contact.connect('room-subject', self._on_room_subject)

        self._room_config_form = None

        self._ui = get_builder('groupchat_manage.ui')
        self.append(self._ui.stack)

        self._avatar_chooser_button = AvatarFileChooserButton(
            tooltip=_('Change your profile picture'),
            icon_name='document-edit-symbolic',
        )
        self._avatar_chooser_button.set_halign(Gtk.Align.END)
        self._avatar_chooser_button.set_valign(Gtk.Align.END)
        self._avatar_chooser_button.set_visible(False)

        self._ui.avatar_overlay.add_overlay(self._avatar_chooser_button)

        self._connect(self._ui.remove_avatar_button, 'clicked', self._on_remove_avatar)
        self._connect(
            self._ui.muc_description_entry, 'changed', self._on_name_desc_changed
        )
        self._connect(self._ui.muc_name_entry, 'changed', self._on_name_desc_changed)
        self._connect(self._ui.destroy_muc_button, 'clicked', self._on_destroy_clicked)
        self._connect(
            self._ui.manage_save_button, 'clicked', self._on_manage_save_clicked
        )
        self._connect(
            self._ui.subject_change_button, 'clicked', self._on_subject_change_clicked
        )
        self._connect(
            self._ui.avatar_update_button, 'clicked', self._on_avatar_update_clicked
        )
        self._connect(
            self._ui.avatar_cancel_button, 'clicked', self._on_avatar_cancel_clicked
        )
        self._connect(
            self._ui.destroy_alternate_entry,
            'changed',
            self._on_destroy_alternate_changed,
        )
        self._connect(
            self._ui.destroy_cancel_button, 'clicked', self._on_destroy_cancelled
        )
        self._connect(self._ui.destroy_button, 'clicked', self._on_destroy_confirmed)
        self._connect(
            self._ui.subject_textview.get_buffer(),
            'changed',
            self._on_subject_text_changed,
        )
        self._connect(
            self._avatar_chooser_button, 'path-picked', self._on_avatar_picked
        )

        self._avatar_selector = AvatarSelector()
        self._avatar_selector.set_size_request(500, 500)
        self._ui.avatar_selector_grid.attach(self._avatar_selector, 0, 1, 1, 1)

        self._prepare_subject()
        self._prepare_manage()

    def do_unroot(self) -> None:
        self._disconnect_all()
        Gtk.Box.do_unroot(self)
        del self._avatar_selector
        del self._avatar_chooser_button
        del self._room_config_form
        app.check_finalize(self)

    @property
    def disco_info(self):
        return app.storage.cache.get_last_disco_info(self._contact.jid)

    def _prepare_subject(self) -> None:
        text = ''
        if self._contact.subject is not None:
            text = self._contact.subject.text

        self._ui.subject_textview.get_buffer().set_text(text)
        self._ui.subject_textview.set_sensitive(
            self._is_subject_change_allowed())

    def _is_subject_change_allowed(self) -> bool:
        if not self._contact.is_joined:
            return False

        self_contact = self._contact.get_self()
        if self_contact is None:
            return False

        if self_contact.affiliation in (Affiliation.OWNER, Affiliation.ADMIN):
            return True

        if self.disco_info is None:
            return False
        return self.disco_info.muc_subjectmod or False

    def _on_subject_text_changed(self, buffer_: Gtk.TextBuffer) -> None:
        text = buffer_.get_text(buffer_.get_start_iter(),
                                buffer_.get_end_iter(),
                                False)

        assert self._contact.subject is not None

        self._ui.subject_change_button.set_sensitive(
            text != self._contact.subject.text)

    def _on_subject_change_clicked(self, _button: Gtk.Button) -> None:
        buffer_ = self._ui.subject_textview.get_buffer()
        subject = buffer_.get_text(buffer_.get_start_iter(),
                                   buffer_.get_end_iter(),
                                   False)
        self._client.get_module('MUC').set_subject(self._contact.jid, subject)

    def _on_room_subject(self,
                         _contact: GroupchatContact,
                         _signal_name: str,
                         subject: MucSubject
                         ) -> None:

        self._ui.subject_textview.get_buffer().set_text(subject.text)

        assert self._contact.subject is not None
        self._ui.subject_change_button.set_sensitive(
            subject.text != self._contact.subject.text)

    def _prepare_manage(self) -> None:
        joined = self._contact.is_joined
        vcard_support = False

        if self.disco_info is not None:
            vcard_support = self.disco_info.supports(Namespace.VCARD)

            self._ui.muc_name_entry.set_text(self.disco_info.muc_name or '')
            self._ui.muc_description_entry.set_text(
                self.disco_info.muc_description or '')

        self.update_avatar()

        self_contact = self._contact.get_self()
        if not joined or self_contact is None:
            return

        if vcard_support and self_contact.affiliation.is_owner:
            self._avatar_chooser_button.show()
            self._ui.remove_avatar_button.show()

        if self_contact.affiliation.is_owner:
            self._client.get_module('MUC').request_config(
                self._contact.jid, callback=self._on_manage_form_received)
            self._ui.destroy_muc_button.set_sensitive(True)

    def _on_manage_form_received(self, task: Task) -> None:
        try:
            result = task.finish()
        except StanzaError as error:
            log.info(error)
            return

        self._ui.muc_name_entry.set_sensitive(True)
        self._ui.muc_description_entry.set_sensitive(True)
        self._room_config_form = result.form

    def _on_name_desc_changed(self, _entry: Gtk.Entry) -> None:
        if self.disco_info is not None:
            disco_name = self.disco_info.muc_name or ''
            disco_desc = self.disco_info.muc_description or ''
            name = self._ui.muc_name_entry.get_text()
            desc = self._ui.muc_description_entry.get_text()

            self._ui.manage_save_button.set_sensitive(
                desc != disco_desc or name != disco_name)

    def _on_manage_save_clicked(self, _button: Gtk.Button) -> None:
        if self._room_config_form is not None:
            name = self._ui.muc_name_entry.get_text()
            desc = self._ui.muc_description_entry.get_text()
            try:
                name_field = self._room_config_form['muc#roomconfig_roomname']
                desc_field = self._room_config_form['muc#roomconfig_roomdesc']
            except KeyError:
                pass
            else:
                name_field.value = name
                desc_field.value = desc

            self._client.get_module('MUC').set_config(
                self._contact.jid, self._room_config_form)

    def _on_avatar_cancel_clicked(self, _button: Gtk.Button) -> None:
        self._ui.stack.set_visible_child_name('manage')

    def _on_remove_avatar(self, _button: Gtk.Button) -> None:
        vcard = VCard()
        self._client.get_module('VCardTemp').set_vcard(
            vcard,
            jid=self._contact.jid,
            callback=self._on_upload_avatar_result)

    def _on_avatar_picked(
        self, _button: AvatarFileChooserButton, paths: list[Path]
    ) -> None:
        self._avatar_selector.prepare_crop_area(str(paths[0]))
        self._ui.avatar_update_button.set_sensitive(
            self._avatar_selector.get_prepared())
        self._ui.stack.set_visible_child_name('avatar')

    def _on_upload_avatar_result(self, task: Task) -> None:
        try:
            task.finish()
        except Exception as error:
            ErrorDialog(_('Uploading Avatar Failed'),
                        _('Uploading avatar image failed: %s') % error)

    def _on_avatar_update_clicked(self, _button: Gtk.Button) -> None:
        success, data, _w, _h = self._avatar_selector.get_avatar_bytes()
        if not success:
            ErrorDialog(_('Loading Avatar Failed'),
                        _('Loading avatar image failed'))
            return

        assert data
        sha = app.app.avatar_storage.save_avatar(data)
        if sha is None:
            ErrorDialog(_('Saving Avatar Failed'),
                        _('Saving avatar image failed'))
            return

        vcard = VCard()
        vcard.set_avatar(data, 'image/png')  # pyright: ignore

        self._client.get_module('VCardTemp').set_vcard(
            vcard,
            jid=self._contact.jid,
            callback=self._on_upload_avatar_result)
        self._ui.stack.set_visible_child_name('manage')

    def update_avatar(self):
        texture = app.app.avatar_storage.get_muc_texture(
            self._account,
            self._contact.jid,
            AvatarSize.GROUP_INFO,
            self.get_scale_factor())

        self._ui.avatar_button_image.set_pixel_size(AvatarSize.GROUP_INFO)
        self._ui.avatar_button_image.set_from_paintable(texture)

    def _on_destroy_clicked(self, _button: Gtk.Button) -> None:
        self._ui.stack.set_visible_child_name('destroy')
        self._ui.destroy_reason_entry.grab_focus()
        # TODO GTK4
        # self._ui.destroy_button.grab_default()

    def _on_destroy_alternate_changed(self, entry: Gtk.Entry) -> None:
        jid = entry.get_text()
        if jid:
            try:
                jid = validate_jid(jid)
            except Exception:
                icon = 'dialog-warning-symbolic'
                text = _('Invalid XMPP Address')
                self._ui.destroy_alternate_entry.set_icon_from_icon_name(
                    Gtk.EntryIconPosition.SECONDARY, icon)
                self._ui.destroy_alternate_entry.set_icon_tooltip_text(
                    Gtk.EntryIconPosition.SECONDARY, text)
                self._ui.destroy_button.set_sensitive(False)
                return
        self._ui.destroy_alternate_entry.set_icon_from_icon_name(
            Gtk.EntryIconPosition.SECONDARY, None)
        self._ui.destroy_button.set_sensitive(True)

    def _on_destroy_confirmed(self, _button: Gtk.Button) -> None:
        reason = self._ui.destroy_reason_entry.get_text()
        alternate_jid = self._ui.destroy_alternate_entry.get_text()
        self._client.get_module('MUC').destroy(
            self._contact.jid, reason, alternate_jid)

        window = cast(GajimAppWindow, get_app_window('GroupchatDetails'))
        window.close()

    def _on_destroy_cancelled(self, _button: Gtk.Button) -> None:
        self._ui.stack.set_visible_child_name('manage')
