package server

import (
	"fmt"
	"log"
	"net/http"
	"testing"
	"time"

	"github.com/openp2p-cn/inp2p/pkg/config"
	"github.com/openp2p-cn/inp2p/pkg/nat"
	"github.com/openp2p-cn/inp2p/pkg/protocol"
	"github.com/openp2p-cn/inp2p/pkg/signal"
	"github.com/gorilla/websocket"
)

func TestLoginFlow(t *testing.T) {
	// Start server
	cfg := config.DefaultServerConfig()
	cfg.WSPort = 29300
	cfg.Token = 999

	srv := New(cfg)
	mux := http.NewServeMux()
	mux.HandleFunc("/ws", srv.HandleWS)
	go http.ListenAndServe(fmt.Sprintf(":%d", cfg.WSPort), mux)
	time.Sleep(200 * time.Millisecond)

	// Connect as client manually
	ws, _, err := websocket.DefaultDialer.Dial(fmt.Sprintf("ws://127.0.0.1:%d/ws", cfg.WSPort), nil)
	if err != nil {
		t.Fatal(err)
	}
	conn := signal.NewConn(ws)
	defer conn.Close()

	// Start read loop in background
	go conn.ReadLoop()

	// Send login
	loginReq := protocol.LoginReq{
		Node:    "testNode",
		Token:   999,
		Version: "test",
		NATType: protocol.NATCone,
	}

	rspData, err := conn.Request(
		protocol.MsgLogin, protocol.SubLoginReq, loginReq,
		protocol.MsgLogin, protocol.SubLoginRsp,
		5*time.Second,
	)
	if err != nil {
		t.Fatalf("login request failed: %v", err)
	}

	var rsp protocol.LoginRsp
	protocol.DecodePayload(rspData, &rsp)
	if rsp.Error != 0 {
		t.Fatalf("login error: %d %s", rsp.Error, rsp.Detail)
	}
	log.Printf("Login OK: node=%s", rsp.Node)

	// Verify node is registered
	time.Sleep(100 * time.Millisecond)
	nodes := srv.GetOnlineNodes()
	if len(nodes) != 1 {
		t.Fatalf("expected 1 node, got %d", len(nodes))
	}
	if nodes[0].Name != "testNode" {
		t.Fatalf("expected testNode, got %s", nodes[0].Name)
	}

	srv.Stop()
}

func TestTwoClientsWithSTUN(t *testing.T) {
	cfg := config.DefaultServerConfig()
	cfg.WSPort = 29301
	cfg.STUNUDP1 = 29382
	cfg.STUNUDP2 = 29384
	cfg.STUNTCP1 = 29380
	cfg.STUNTCP2 = 29381
	cfg.Token = 888

	// STUN
	stunQuit := make(chan struct{})
	defer close(stunQuit)
	go nat.ServeUDPSTUN(cfg.STUNUDP1, stunQuit)
	go nat.ServeUDPSTUN(cfg.STUNUDP2, stunQuit)
	go nat.ServeTCPSTUN(cfg.STUNTCP1, stunQuit)
	go nat.ServeTCPSTUN(cfg.STUNTCP2, stunQuit)

	srv := New(cfg)
	srv.StartCleanup()
	mux := http.NewServeMux()
	mux.HandleFunc("/ws", srv.HandleWS)
	go http.ListenAndServe(fmt.Sprintf(":%d", cfg.WSPort), mux)
	time.Sleep(300 * time.Millisecond)

	// NAT detect
	natResult := nat.Detect("127.0.0.1", cfg.STUNUDP1, cfg.STUNUDP2, cfg.STUNTCP1, cfg.STUNTCP2)
	log.Printf("NAT: type=%s publicIP=%s", natResult.Type, natResult.PublicIP)

	// Client A
	connectClient := func(name string, relay bool) *signal.Conn {
		ws, _, err := websocket.DefaultDialer.Dial(fmt.Sprintf("ws://127.0.0.1:%d/ws", cfg.WSPort), nil)
		if err != nil {
			t.Fatalf("dial %s: %v", name, err)
		}
		conn := signal.NewConn(ws)
		go conn.ReadLoop()

		rspData, err := conn.Request(
			protocol.MsgLogin, protocol.SubLoginReq,
			protocol.LoginReq{Node: name, Token: 888, Version: "test", NATType: natResult.Type, RelayEnabled: relay},
			protocol.MsgLogin, protocol.SubLoginRsp,
			5*time.Second,
		)
		if err != nil {
			t.Fatalf("login %s: %v", name, err)
		}
		var rsp protocol.LoginRsp
		protocol.DecodePayload(rspData, &rsp)
		if rsp.Error != 0 {
			t.Fatalf("login %s error: %s", name, rsp.Detail)
		}
		log.Printf("%s login ok", name)
		return conn
	}

	connA := connectClient("nodeA", true)
	defer connA.Close()
	connB := connectClient("nodeB", false)
	defer connB.Close()

	time.Sleep(200 * time.Millisecond)
	nodes := srv.GetOnlineNodes()
	if len(nodes) != 2 {
		t.Fatalf("expected 2 nodes, got %d", len(nodes))
	}

	// Test relay node discovery
	relays := srv.GetRelayNodes("", "nodeB")
	if len(relays) != 1 || relays[0].Name != "nodeA" {
		t.Fatalf("expected nodeA as relay, got %v", relays)
	}
	log.Printf("Relay nodes: %v", relays[0].Name)

	srv.Stop()
}
