"""Network browser — discovers NomadNet nodes and fetches pages via Reticulum.

Starts an RNS transport on FastAPI startup, listens for NomadNet node
announces, and exposes discovered nodes + remote page fetching via API.
"""

from __future__ import annotations

import asyncio
import logging
import os
import threading
import time
from pathlib import Path

from fastapi import APIRouter, Query

router = APIRouter()
log = logging.getLogger("browse")

# ---------------------------------------------------------------------------
# Module-level state
# ---------------------------------------------------------------------------

_nodes: dict[str, dict] = {}  # hash_hex -> node info
_own_hash: str | None = None
_own_name: str = os.environ.get("NOMADNET_NODE_NAME", "Micronomicon")
_lock = threading.Lock()
_started = False


# ---------------------------------------------------------------------------
# RNS lifecycle
# ---------------------------------------------------------------------------

def start_browser() -> None:
    """Initialize RNS and begin listening for NomadNet node announces."""
    global _started, _own_hash

    if _started:
        return

    try:
        import RNS

        configdir = os.environ.get("RNS_CONFIG_DIR", None)
        if configdir:
            Path(configdir).mkdir(parents=True, exist_ok=True)

        reticulum = RNS.Reticulum(configdir=configdir)

        # Register handler for NomadNet page-serving node announces
        RNS.Transport.register_announce_handler(
            _on_announce,
            aspect_filter="nomadnetwork.node",
        )

        _started = True
        log.info("RNS browser started (v%s)", RNS.__version__)

    except Exception as exc:
        log.warning("Failed to start RNS browser: %s", exc)


def _on_announce(
    destination_hash: bytes,
    announced_identity,
    app_data: bytes | None,
) -> None:
    """Handle an incoming NomadNet node announce."""
    import RNS

    hash_hex = RNS.hexrep(destination_hash, delimit=False)

    name = hash_hex[:12]
    if app_data:
        try:
            name = app_data.decode("utf-8")
        except Exception:
            pass

    is_self = name == _own_name

    with _lock:
        _nodes[hash_hex] = {
            "hash": hash_hex,
            "name": name,
            "last_seen": time.time(),
            "is_self": is_self,
        }
        if is_self:
            global _own_hash
            _own_hash = hash_hex

    log.info("Node announce: %s (%s)%s", name, hash_hex[:8], " [self]" if is_self else "")


# ---------------------------------------------------------------------------
# API endpoints
# ---------------------------------------------------------------------------

@router.get("/browse/nodes")
async def list_nodes():
    """Return all discovered NomadNet page-serving nodes."""
    with _lock:
        nodes = list(_nodes.values())

    # Ensure the user's own node is always present
    if not any(n["is_self"] for n in nodes):
        nodes.insert(0, {
            "hash": _own_hash or "self",
            "name": _own_name,
            "last_seen": time.time(),
            "is_self": True,
        })

    return nodes


@router.get("/browse/page/{hash_hex}")
async def get_remote_page(hash_hex: str, path: str = Query("index.mu")):
    """Fetch a Micron page from a node.

    For the user's own node reads from the local pages directory.
    For remote nodes establishes an RNS link and requests the page.
    """
    # Own node → read from disk
    with _lock:
        node = _nodes.get(hash_hex)
    if (node and node.get("is_self")) or hash_hex == "self":
        return _read_local_page(path)

    # Remote node → RNS request
    content = await _request_remote_page(hash_hex, path)
    if content is None:
        return {"content": None, "error": "Could not reach node"}
    return {"content": content}


# ---------------------------------------------------------------------------
# Local page reader
# ---------------------------------------------------------------------------

def _read_local_page(path: str) -> dict:
    pages_dir = os.environ.get("PAGES_DIR", str(Path.home() / ".nomadnetwork/storage/pages"))
    filepath = Path(pages_dir) / path
    try:
        return {"content": filepath.read_text()}
    except FileNotFoundError:
        return {"content": None, "error": "Page not found"}


# ---------------------------------------------------------------------------
# Remote page fetcher (RNS link + request/response)
# ---------------------------------------------------------------------------

async def _request_remote_page(hash_hex: str, path: str) -> str | None:
    """Establish an RNS link to a remote NomadNet node and request a page."""
    try:
        import RNS
    except ImportError:
        return None

    loop = asyncio.get_event_loop()
    future: asyncio.Future[str | None] = loop.create_future()

    def _do_request():
        try:
            dest_hash = bytes.fromhex(hash_hex)

            # Ensure path to destination is known
            if not RNS.Transport.has_path(dest_hash):
                RNS.Transport.request_path(dest_hash)
                deadline = time.time() + 10
                while time.time() < deadline:
                    if RNS.Transport.has_path(dest_hash):
                        break
                    time.sleep(0.1)
                else:
                    loop.call_soon_threadsafe(future.set_result, None)
                    return

            identity = RNS.Identity.recall(dest_hash)
            if not identity:
                loop.call_soon_threadsafe(future.set_result, None)
                return

            dest = RNS.Destination(
                identity,
                RNS.Destination.OUT,
                RNS.Destination.SINGLE,
                "nomadnetwork",
                "node",
            )

            link = RNS.Link(dest)

            # Wait for link to become active
            deadline = time.time() + 15
            while time.time() < deadline:
                if link.status == RNS.Link.ACTIVE:
                    break
                time.sleep(0.1)
            else:
                link.teardown()
                loop.call_soon_threadsafe(future.set_result, None)
                return

            # Request the page via NomadNet's protocol
            def on_response(request_receipt):
                try:
                    resp = request_receipt.response
                    if resp is not None:
                        content = resp.decode("utf-8") if isinstance(resp, bytes) else str(resp)
                        loop.call_soon_threadsafe(future.set_result, content)
                    else:
                        loop.call_soon_threadsafe(future.set_result, None)
                except Exception:
                    loop.call_soon_threadsafe(future.set_result, None)
                finally:
                    link.teardown()

            def on_failed(request_receipt):
                if not future.done():
                    loop.call_soon_threadsafe(future.set_result, None)
                link.teardown()

            link.request(
                "/page/" + path,
                response_callback=on_response,
                failed_callback=on_failed,
            )

        except Exception as exc:
            log.warning("Remote page request failed: %s", exc)
            if not future.done():
                loop.call_soon_threadsafe(future.set_result, None)

    # Run blocking RNS operations in a thread
    threading.Thread(target=_do_request, daemon=True).start()

    try:
        return await asyncio.wait_for(future, timeout=30.0)
    except asyncio.TimeoutError:
        return None
