"""
nodelink tests
"""
import asyncio
import json
import subprocess
import sys
import time
import threading

import pytest
import websockets

from nodelink import Server, Client, Player


# ------------------------------------------------------------------ #
# Player                                                               #
# ------------------------------------------------------------------ #

def test_player_state_basic():
    p = Player("abc")
    p.update_state({"x": 10, "y": 5})
    assert p.state == {"x": 10, "y": 5}
    assert p.state_changed()
    p.mark_synced()
    assert not p.state_changed()


def test_player_delta_changed():
    p = Player("abc")
    p.update_state({"x": 1, "y": 2})
    p.mark_synced()
    p.set_state("x", 99)
    assert p.delta() == {"x": 99}


def test_player_delta_deleted_keys():
    p = Player("abc")
    p.update_state({"x": 1, "hp": 100})
    p.mark_synced()
    del p.state["hp"]
    d = p.delta()
    assert d.get("hp") is None, "deleted key should appear as None in delta"


def test_player_get_state_default():
    p = Player("abc")
    assert p.get_state("missing", "default") == "default"


def test_player_clear_state():
    p = Player("abc")
    p.update_state({"x": 1})
    p.clear_state()
    assert p.state == {}


# ------------------------------------------------------------------ #
# Server + Client integration                                          #
# ------------------------------------------------------------------ #

PORT = 15100


def run_server(port):
    server = Server(port=port)

    @server.on("connect")
    def on_connect(player):
        asyncio.ensure_future(player.send("welcome", {"id": player.id}))

    @server.on("echo")
    def on_echo(player, data):
        asyncio.ensure_future(player.send("echo_reply", data))

    @server.on("set_state")
    def on_set_state(player, data):
        player.update_state(data)

    server.start()


def test_connect_and_echo():
    t = threading.Thread(target=run_server, args=(PORT,), daemon=True)
    t.start()
    time.sleep(0.3)

    results = []

    async def run():
        async with websockets.connect(f"ws://localhost:{PORT}") as ws:
            # Should get welcome
            raw = await asyncio.wait_for(ws.recv(), timeout=2)
            msg = json.loads(raw)
            assert msg["event"] == "welcome"
            results.append("welcome")

            # Send echo
            await ws.send(json.dumps({"event": "echo", "data": {"val": 42}}))
            raw = await asyncio.wait_for(ws.recv(), timeout=2)
            msg = json.loads(raw)
            assert msg["event"] == "echo_reply"
            assert msg["data"]["val"] == 42
            results.append("echo")

    asyncio.run(run())
    assert results == ["welcome", "echo"]


def test_send_queue():
    """Messages sent before connect() should be delivered after connecting."""
    client = Client("localhost", PORT)
    received = []

    @client.on("welcome")
    def on_welcome(data):
        received.append("welcome")

    @client.on("echo_reply")
    def on_echo(data):
        received.append(data["val"])

    # Send BEFORE connecting — should be queued
    client.send("echo", {"val": 99})

    def stop_after():
        time.sleep(1.5)
        import os, signal
        os.kill(os.getpid(), signal.SIGINT)

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

    try:
        client.connect()
    except (KeyboardInterrupt, SystemExit):
        pass

    assert "welcome" in received
    assert 99 in received, "queued message should have been delivered"
