"""Sign-in inside Blender: the modal device-flow operator + verdict cache.

All HTTP runs on worker threads (api.login_flow / api.validate) — the modal
timer only drains a queue, so the UI never freezes (plan §5). The panel reads
`STATE` (idle | waiting | error) and `VERDICT` (the last /api/tools/validate
response) from here; this module never imports panels at module level to stay
acyclic (panels imports auth).
"""

from __future__ import annotations

import queue
import threading

import bpy

from . import host
from ._core import api, config

# The sidebar's login status. phase: "idle" | "waiting" | "error".
STATE = {"phase": "idle", "message": ""}

# Last validate response ({entitled, plan, planDisplay, …}) or None. In-memory
# only — the panel shows it when known, says nothing when not.
VERDICT = None


def _redraw_views():
    for window in bpy.context.window_manager.windows:
        for area in window.screen.areas:
            if area.type == "VIEW_3D":
                area.tag_redraw()


def _invalidate_panels():
    from . import panels

    panels.invalidate_status()


def set_verdict(verdict):
    global VERDICT
    VERDICT = verdict


def refresh_verdict_async():
    """Re-validate the stored credential off-thread; updates VERDICT + redraws."""
    if not host.online_allowed():
        return
    site = host.site_url()
    credential = config.load_credential(site)
    if not credential:
        set_verdict(None)
        return

    def worker():
        try:
            status, data = host.validate(site, credential["token"])
            set_verdict(data if status == 200 else None)
            # Heal a nameless stored credential (e.g. saved while validate was
            # unreachable): the panel and CLI then have a name even offline.
            display = api.display_name(VERDICT)
            if display and not credential.get("name"):
                config.save_credential(site, credential["token"], name=display)
        except Exception:
            set_verdict(None)  # explicit refresh failed; installed tools stay local
        # tag_redraw from a thread is tolerated, but schedule on the main loop to be safe.
        bpy.app.timers.register(_deferred_redraw, first_interval=0.0)

    threading.Thread(target=worker, daemon=True).start()


def _deferred_redraw():
    _invalidate_panels()
    _redraw_views()
    return None  # one-shot timer


class TB_OT_login(bpy.types.Operator):
    """Device-flow sign-in: opens your browser, waits for authorization."""

    bl_idname = "three_blocks.login"
    bl_label = "Sign in to Three Blocks"
    bl_description = (
        "Sign in with your browser — one seat per person. The credential is "
        "shared with the three-blocks CLI"
    )

    def invoke(self, context, event):
        if not host.online_allowed():
            self.report(
                {"ERROR"},
                "Online access is disabled (Preferences ▸ System). Enable it, or run `three-blocks login` in a terminal.",
            )
            return {"CANCELLED"}
        if STATE["phase"] == "waiting":
            self.report({"INFO"}, "A sign-in is already waiting for the browser")
            return {"CANCELLED"}

        self._site = host.site_url()
        self._queue = queue.Queue()
        self._stop = threading.Event()
        self._thread = threading.Thread(
            target=host.login_flow,
            args=(self._site, self._queue.put, self._stop.is_set),
            daemon=True,
        )
        self._timer = context.window_manager.event_timer_add(0.25, window=context.window)
        context.window_manager.modal_handler_add(self)
        STATE.update(phase="waiting", message="Contacting the site…")
        self._thread.start()
        _redraw_views()
        return {"RUNNING_MODAL"}

    def modal(self, context, event):
        if event.type == "ESC":
            self._stop.set()
            return self._finish(context, "idle", "", {"INFO"}, "Sign-in cancelled", "CANCELLED")
        if event.type != "TIMER":
            return {"PASS_THROUGH"}  # login waits in the background; keep working

        try:
            item = self._queue.get_nowait()
        except queue.Empty:
            return {"RUNNING_MODAL"}

        kind = item[0]
        if kind == "open_url":
            bpy.ops.wm.url_open(url=item[1])
            STATE.update(phase="waiting", message="Authorize in your browser… (Esc cancels)")
            _redraw_views()
            return {"RUNNING_MODAL"}
        if kind == "token":
            payload = item[1]
            config.save_credential(self._site, payload["token"], name=payload.get("name"))
            set_verdict(payload.get("verdict"))
            _invalidate_panels()
            # Re-fetch the catalog WITH the fresh credential — the per-addon
            # entitled flags (the panel's locks) are anonymous until now.
            from . import catalog

            catalog.check_async()
            who = payload.get("name") or "your account"
            return self._finish(context, "idle", "", {"INFO"}, f"Signed in as {who}", "FINISHED")
        if kind == "cancelled":
            return self._finish(context, "idle", "", {"INFO"}, "Sign-in cancelled", "CANCELLED")
        # ("error", message)
        return self._finish(context, "error", item[1], {"ERROR"}, item[1], "CANCELLED")

    def _finish(self, context, phase, message, level, report, result):
        context.window_manager.event_timer_remove(self._timer)
        STATE.update(phase=phase, message=message)
        _redraw_views()
        self.report(level, report)
        return {result}


classes = (TB_OT_login,)


def register():
    for cls in classes:
        bpy.utils.register_class(cls)


def unregister():
    for cls in reversed(classes):
        bpy.utils.unregister_class(cls)
    STATE.update(phase="idle", message="")
    set_verdict(None)
