import datetime
from gettext import gettext as _
from gi.repository import Gio
from gi.repository import GObject
from gi.repository import Gtk
from gi.repository import Adw

from . import local
from . import models
from .networkconnectionmonitor import NetworkConnectionMonitor
from .widgets import InputDialog
from .widgets import EventEditDialog
from .widgets import LoadingOverlay


@Gtk.Template(resource_path="/net/kirgroup/confy/openwindow.ui")
class OpenWindow(Adw.ApplicationWindow):
    __gtype_name__ = "ConfyOpenWindow"

    overlay = Gtk.Template.Child()  # type: ignore
    searchbar = Gtk.Template.Child()  # type: ignore
    stack = Gtk.Template.Child()  # type: ignore
    empty_placeholder = Gtk.Template.Child()  # type: ignore
    nomatch_placeholder = Gtk.Template.Child()  # type: ignore
    content = Gtk.Template.Child()  # type: ignore

    heading_next = Gtk.Template.Child()  # type: ignore
    listbox_next = Gtk.Template.Child()  # type: ignore
    heading_past = Gtk.Template.Child()  # type: ignore
    listbox_past = Gtk.Template.Child()  # type: ignore

    _in_edit = False

    @GObject.Property(type=bool, default=False)
    def in_edit(self):
        return self._in_edit

    @in_edit.setter
    def in_edit_setter(self, value):
        self._in_edit = value

    def __init__(self, **kwargs):
        self.nm = NetworkConnectionMonitor()
        self.data = []

        super().__init__(**kwargs)

        self.stack.set_visible_child(self.empty_placeholder)

        fetcher = local.open_menu(self.nm.props.isconnected)
        if fetcher is not None:
            self.loading_show(fetcher)
            fetcher.connect("done", lambda *_: self.update_list())
            fetcher.connect("error", lambda s, e: self.notification_show(_("<b>Error loading events list:</b>\n{}").format(e)))
        else:
            self.update_list()

        self._add_action("add-event", None, ["<Primary>a"], self.open_custom_url)
        self._add_action("refresh-events", None, ["<Primary>r"], self.update_schedules)
        # TODO: disable action if there is no user entris in menu
        self.edit_meta_action = self._add_action("edit-meta", None, None, lambda *_: self.set_in_edit(not self.props.in_edit))
        self._add_action("search", None, ["<Primary>f"], lambda *_: self.searchbar.set_search_mode(True))

        # can't do this because: Warning: ../glib/gobject/gbinding.c:271: Unable to convert a value of type PyObject to a value of type gboolean
        # self.nm.bind_property("isconnected", refresh_events_action, "enabled", 0 )
        self.nm.connect("notify", self.on_networkconnectionmonitor_notify)
        self.on_networkconnectionmonitor_notify()

    def _add_action(self, name, parameter_type, accels, activate_cbk):
        action = Gio.SimpleAction.new(name, parameter_type)
        action.connect("activate", activate_cbk)
        self.add_action(action)
        if accels is not None:
            self.get_application().set_accels_for_action(F"win.{name}", accels)
        return action

    def set_in_edit(self, value):
        self.props.in_edit = value

    @Gtk.Template.Callback()
    def on_do_search(self, *args):
        self.update_list()

    def on_networkconnectionmonitor_notify(self, *args):
        is_connected = self.nm.props.isconnected
        self.lookup_action("refresh-events").set_enabled(is_connected)
        self.lookup_action("add-event").set_enabled(is_connected)

    def update_schedules(self, *args):
        def _done(s, f):
            self.update_list()
            self.notification_show(_("<b>Events list updated</b>"))

        def _error(s, e):
            self.notification_show(_("<b>Error loading events list:</b>\n{}").format(e))

        fetcher = local.update_menu()
        self.loading_show(fetcher)
        fetcher.connect("done", _done)
        fetcher.connect("error", _error)

    def delete_conf(self, conf, cbk=None):
        def _message_response(dialog, response):
            if response == "delete":
                # delete from user menu
                local.delete_user_menu(conf.to_json())
                # delete from recent list
                Gtk.RecentManager.get_default().remove_item(conf.url)
                self.update_list()
                if cbk is not None:
                    cbk(conf)
            dialog.destroy()
        message = Adw.MessageDialog(transient_for=self)
        message.props.heading = _('Delete event "{}"?').format(conf.title)
        message.props.body = _("Event will be removed from event list and related data will be deleted")
        message.add_response("cancel", _("Cancel"))
        message.add_response("delete", _("Delete"))
        message.set_response_appearance("delete", Adw.ResponseAppearance.DESTRUCTIVE)
        message.set_default_response("cancel")
        message.set_close_response("cancel")
        message.connect("response", _message_response)
        message.present()

    def edit_conf(self, conf, ok_cbk=None, del_cbk=None, can_cancel=True, can_delete=False):
        def _save(dialog):
            conf = d.get_conf()
            local.update_user_menu(conf.to_json())
            if conf.has_cache():
                with conf.get_meta() as m:
                    m.title = conf.title
                    m.start = conf.start
                    m.end = conf.end
            self.update_list()
            if ok_cbk is not None:
                ok_cbk(conf)
            d.close()

        def _delete(dialog):
            self.delete_conf(conf, lambda *_: d.close())

        if conf is None:
            return
        d = EventEditDialog(self, conf, can_delete=can_delete, can_cancel=can_cancel)
        d.connect("save", _save)
        d.connect("delete", _delete)
        d.connect("cancel", lambda *args: d.close())
        d.present(self)

    def open_custom_url(self, *args):
        def _cbk(conf):
            self.notification_show(_("<b>New event:</b> {}").format(conf.title))

        def _error(s, e):
            self.notification_show(_("<b>Error:</b> {}").format(e))

        def _add_menu_ckb(meta):
            conf = models.Conference.by_url(meta.url)
            self.edit_conf(conf, can_cancel=False, ok_cbk=_cbk)

        def _dialog_response(dialog, res):
            if res == InputDialog.OK:
                url = dialog.get_text()
                try:
                    fetcher = local.add_user_menu(url, _add_menu_ckb)
                    if fetcher is not None:
                        fetcher.connect("error", _error)
                except local.MenuItemAlreadyExistsException as e:
                    # edit existing obj
                    self.notification_show(_("Event already in list"))
                    conf = models.Conference()._init(e.obj)
                    self.edit_conf(conf, can_cancel=True)
                except Exception as e:
                    import traceback
                    traceback.print_exc()

                    self.notification_show(_("<b>Error:</b> {}").format(e))
                else:
                    if fetcher is not None:
                        self.loading_show(fetcher)
            dialog.destroy()

        dialog = InputDialog(self)
        dialog.connect("response", _dialog_response)
        dialog.present()

    def _empty_list(self, listbox):
        w = listbox.get_last_child()
        while w:
            listbox.remove(w)
            w = listbox.get_last_child()

    def update_list(self):
        sb = self.searchbar
        try:
            confs = models.Conference.all()

            # if searchbar is visible and search text is not "", filter confs by title and url
            if sb.is_searching():
                search = sb.entry.get_text().lower()
                confs = [conf for conf in confs
                         if search in conf.title.lower() or search in conf.url.lower()]
        except Exception as e:
            self.notification_show(_("<b>Error loading events list:</b>\n{}").format(e))

        self._empty_list(self.listbox_next)
        self._empty_list(self.listbox_past)

        count = len(confs)

        if count == 0:
            if self.searchbar.is_searching():
                self.stack.set_visible_child(self.nomatch_placeholder)
            else:
                self.stack.set_visible_child(self.empty_placeholder)
        else:
            self.stack.set_visible_child(self.content)

        confs_next = []
        confs_past = []

        for conf in confs:
            row = self.build_row(conf)
            if conf.end < datetime.date.today():
                confs_past.append(conf)
                self.listbox_past.append(row)
            else:
                confs_next.append(conf)
                self.listbox_next.append(row)

        show_next = len(confs_next) > 0
        show_past = len(confs_past) > 0
        self.listbox_next.set_visible(show_next)
        self.heading_next.set_visible(show_next)
        self.listbox_past.set_visible(show_past)
        self.heading_past.set_visible(show_past)

        self.data = [
            confs_next,
            confs_past
        ]

    def build_row(self, obj):
        row = Adw.ActionRow(activatable=True, selectable=False)
        row.set_title(obj.title)

        self.bind_property("in-edit", row, "activatable", GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.INVERT_BOOLEAN)

        if obj.user:
            delete_button = Gtk.Button(
                icon_name="user-trash-symbolic",
                valign=Gtk.Align.CENTER,
            )
            delete_button.add_css_class("destructive-action")
            delete_button.connect("clicked", lambda *_: self.delete_conf(obj))
            self.bind_property("in-edit", delete_button, "visible", GObject.BindingFlags.SYNC_CREATE)
            row.add_suffix(delete_button)

            edit_button = Gtk.Button(
                icon_name="document-edit-symbolic",
                valign=Gtk.Align.CENTER,
            )
            edit_button.add_css_class("suggested-action")
            edit_button.connect("clicked", lambda *_: self.edit_conf(obj, can_delete=obj.user))
            self.bind_property("in-edit", edit_button, "visible", GObject.BindingFlags.SYNC_CREATE)
            row.add_suffix(edit_button)
        return row

    @Gtk.Template.Callback()
    def on_activated_next(self, listbox, actionrow):
        idx = actionrow.get_index()
        obj = self.data[0][idx]
        self.props.application.open_conf(obj)

    @Gtk.Template.Callback()
    def on_activated_past(self, listbox, actionrow):
        idx = actionrow.get_index()
        obj = self.data[1][idx]
        self.props.application.open_conf(obj)

    def loading_show(self, cancellable):
        t = LoadingOverlay(cancellable)
        self.overlay.add_toast(t)

    def notification_show(self, text):
        t = Adw.Toast()
        t.set_title(text)
        self.overlay.add_toast(t)
