from __future__ import annotations

import threading
import time
import warnings
from typing import Any, Callable


def _load_glib_module() -> Any | None:
    try:
        import gi

        warnings.filterwarnings(
            "ignore",
            message="GLib.unix_signal_add_full is deprecated; use GLibUnix.signal_add_full instead",
            category=DeprecationWarning,
        )
        gi.require_version("GLib", "2.0")
        from gi.repository import GLib

        return GLib
    except Exception:
        return None


def default_idle_add(callback: Callable[..., Any], *args: Any) -> Any:
    glib = _load_glib_module()
    if glib is None:
        return callback(*args)
    return glib.idle_add(callback, *args)


def start_background_thread(target: Callable[[], None]) -> threading.Thread:
    thread = threading.Thread(target=target, daemon=True)
    thread.start()
    return thread


class RefreshScheduler:
    def __init__(
        self,
        *,
        idle_add: Callable[..., Any] | None = None,
        thread_runner: Callable[[Callable[[], None]], Any] | None = None,
        time_fn: Callable[[], float] | None = None,
    ) -> None:
        self._idle_add = idle_add or default_idle_add
        self._thread_runner = thread_runner or start_background_thread
        self._time = time_fn or time.time
        self._refresh_generation = 0
        self._pending_refreshes: dict[int, int] = {}

    @staticmethod
    def _account_key(account: Any) -> str:
        tray_key = getattr(account, "tray_key", None)
        if isinstance(tray_key, str) and tray_key:
            return tray_key
        tool = getattr(account, "tool", None)
        slug = getattr(account, "slug", None)
        if isinstance(tool, str) and tool and isinstance(slug, str) and slug:
            return f"{tool}:{slug}"
        return repr(account)

    def refresh_all(
        self,
        accounts: list[Any],
        last_fetched_for: Callable[[Any], float | None],
        interval_seconds: int,
        force: bool,
        schedule_refresh_account: Callable[[Any, int | None], Any],
        on_empty: Callable[[], None],
    ) -> int | None:
        accounts_to_refresh: list[Any] = []
        scheduled_keys: set[str] = set()
        now = self._time()
        for account in accounts:
            last_fetched = last_fetched_for(account)
            if force or last_fetched is None or now - last_fetched > interval_seconds:
                account_key = self._account_key(account)
                if account_key in scheduled_keys:
                    continue
                scheduled_keys.add(account_key)
                accounts_to_refresh.append(account)

        if not accounts_to_refresh:
            on_empty()
            return None

        self._refresh_generation += 1
        generation = self._refresh_generation
        self._pending_refreshes[generation] = len(accounts_to_refresh)
        for account in accounts_to_refresh:
            schedule_refresh_account(account, generation)
        return generation

    def schedule_refresh_account(
        self,
        account: Any,
        generation: int | None,
        run_refresh_account: Callable[[Any, int | None], None],
    ) -> Any:
        return self._thread_runner(
            lambda account=account, generation=generation: run_refresh_account(
                account,
                generation,
            )
        )

    def run_refresh_account(
        self,
        account: Any,
        generation: int | None,
        fetch_snapshot: Callable[[Any], Any],
        apply_snapshot: Callable[[Any, Any, int | None], Any],
        on_error: Callable[[Any, int | None, Exception], Any] | None = None,
    ) -> Any:
        try:
            snapshot = fetch_snapshot(account)
        except Exception as exc:
            if on_error is not None:
                return self._idle_add(on_error, account, generation, exc)
            return None

        def _apply_on_main_loop() -> None:
            try:
                apply_snapshot(account, snapshot, generation)
            except Exception as exc:
                if on_error is not None:
                    on_error(account, generation, exc)

        return self._idle_add(_apply_on_main_loop)

    def finish_refresh(self, generation: int, on_complete: Callable[[], None]) -> None:
        pending = self._pending_refreshes.get(generation)
        if pending is None:
            return
        pending -= 1
        if pending > 0:
            self._pending_refreshes[generation] = pending
            return
        del self._pending_refreshes[generation]
        on_complete()
