import logging
import threading
from collections.abc import Callable
from typing import Any, ClassVar, cast

import gi
from clan_cli.clan_uri import ClanURI
from clan_cli.history.add import HistoryEntry, add_history

from clan_app.components.gkvstore import GKVStore
from clan_app.singletons.use_vms import ClanStore

gi.require_version("Gtk", "4.0")
gi.require_version("Adw", "1")
from gi.repository import Gio, GLib, GObject

log = logging.getLogger(__name__)


class JoinValue(GObject.Object):
    __gsignals__: ClassVar = {
        "join_finished": (GObject.SignalFlags.RUN_FIRST, None, []),
    }

    url: ClanURI
    entry: HistoryEntry | None

    def _join_finished_task(self) -> bool:
        self.emit("join_finished")
        return GLib.SOURCE_REMOVE

    def __init__(self, url: ClanURI) -> None:
        super().__init__()
        self.url: ClanURI = url
        self.entry: HistoryEntry | None = None

    def __join(self) -> None:
        new_entry = add_history(self.url)
        self.entry = new_entry
        GLib.idle_add(self._join_finished_task)

    def join(self) -> None:
        threading.Thread(target=self.__join).start()


class JoinList:
    """
    This is a singleton.
    It is initialized with the first call of use()
    """

    _instance: "None | JoinList" = None
    list_store: Gio.ListStore

    # Make sure the VMS class is used as a singleton
    def __init__(self) -> None:
        raise RuntimeError("Call use() instead")

    @classmethod
    def use(cls: Any) -> "JoinList":
        if cls._instance is None:
            cls._instance = cls.__new__(cls)
            cls.list_store = Gio.ListStore.new(JoinValue)

            ClanStore.use().register_on_deep_change(cls._instance._rerender_join_list)

        return cls._instance

    def _rerender_join_list(
        self, source: GKVStore, position: int, removed: int, added: int
    ) -> None:
        self.list_store.items_changed(
            0, self.list_store.get_n_items(), self.list_store.get_n_items()
        )

    def is_empty(self) -> bool:
        return self.list_store.get_n_items() == 0

    def push(self, uri: ClanURI, after_join: Callable[[JoinValue], None]) -> None:
        """
        Add a join request.
        This method can add multiple join requests if called subsequently for each request.
        """

        value = JoinValue(uri)
        if value.url.machine.get_id() in [
            cast(JoinValue, item).url.machine.get_id() for item in self.list_store
        ]:
            log.info(f"Join request already exists: {value.url}. Ignoring.")
            return

        value.connect("join_finished", self._on_join_finished)
        value.connect("join_finished", after_join)

        self.list_store.append(value)

    def _on_join_finished(self, source: JoinValue) -> None:
        log.info(f"Join finished: {source.url}")
        self.discard(source)
        assert source.entry is not None
        ClanStore.use().push_history_entry(source.entry)

    def discard(self, value: JoinValue) -> None:
        (has, idx) = self.list_store.find(value)
        if has:
            self.list_store.remove(idx)
