package infra

import (
	"context"
	"net/http"
	"net/http/httptest"
	"strings"
	"sync"
	"testing"
	"time"

	"nhooyr.io/websocket"
)

// --- WSHub ---

func TestWSHub(t *testing.T) {
	t.Run("registra y desregistra clientes", func(t *testing.T) {
		hub := NewWSHub()
		go hub.Run()
		defer hub.Stop()

		client := &WSClient{Hub: hub, Send: make(chan []byte, 4), ID: "c1"}
		hub.Register <- client

		// Esperar a que el hub procese
		time.Sleep(20 * time.Millisecond)

		hub.Unregister <- client
		time.Sleep(20 * time.Millisecond)

		// El canal Send del cliente debe estar cerrado tras unregister
		select {
		case _, ok := <-client.Send:
			if ok {
				t.Error("client.Send should be closed after unregister")
			}
		default:
			t.Error("client.Send should be closed (zero-value receive)")
		}
	})

	t.Run("Stop cierra todos los clientes", func(t *testing.T) {
		hub := NewWSHub()
		go hub.Run()

		c1 := &WSClient{Hub: hub, Send: make(chan []byte, 4), ID: "c1"}
		c2 := &WSClient{Hub: hub, Send: make(chan []byte, 4), ID: "c2"}
		hub.Register <- c1
		hub.Register <- c2
		time.Sleep(20 * time.Millisecond)

		hub.Stop()
		time.Sleep(20 * time.Millisecond)

		for _, c := range []*WSClient{c1, c2} {
			select {
			case _, ok := <-c.Send:
				if ok {
					t.Errorf("client %s Send should be closed after Stop", c.ID)
				}
			default:
				t.Errorf("client %s Send should be closed (zero-value receive)", c.ID)
			}
		}
	})

	t.Run("Stop es idempotente", func(t *testing.T) {
		hub := NewWSHub()
		go hub.Run()
		hub.Stop()
		hub.Stop() // no debe panicar
	})
}

// --- WSBroadcast ---

func TestWSBroadcast(t *testing.T) {
	t.Run("envia mensaje al canal Broadcast del hub", func(t *testing.T) {
		hub := NewWSHub()
		err := WSBroadcast(hub, []byte("hola"))
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		select {
		case msg := <-hub.Broadcast:
			if string(msg) != "hola" {
				t.Errorf("got %q, want hola", string(msg))
			}
		case <-time.After(time.Second):
			t.Error("message not in Broadcast channel")
		}
	})

	t.Run("retorna error si hub es nil", func(t *testing.T) {
		err := WSBroadcast(nil, []byte("x"))
		if err == nil {
			t.Error("expected error for nil hub, got nil")
		}
	})

	t.Run("el hub entrega el mensaje a todos los clientes registrados", func(t *testing.T) {
		hub := NewWSHub()
		go hub.Run()
		defer hub.Stop()

		c1 := &WSClient{Hub: hub, Send: make(chan []byte, 4), ID: "c1"}
		c2 := &WSClient{Hub: hub, Send: make(chan []byte, 4), ID: "c2"}
		hub.Register <- c1
		hub.Register <- c2
		time.Sleep(20 * time.Millisecond)

		WSBroadcast(hub, []byte("ping"))
		time.Sleep(20 * time.Millisecond)

		for _, c := range []*WSClient{c1, c2} {
			select {
			case msg := <-c.Send:
				if string(msg) != "ping" {
					t.Errorf("client %s got %q, want ping", c.ID, string(msg))
				}
			case <-time.After(time.Second):
				t.Errorf("client %s did not receive broadcast", c.ID)
			}
		}
	})

	t.Run("cliente lento es desconectado del hub", func(t *testing.T) {
		hub := NewWSHub()
		go hub.Run()
		defer hub.Stop()

		// Cliente con buffer de 1 — el segundo broadcast lo tira
		slow := &WSClient{Hub: hub, Send: make(chan []byte, 1), ID: "slow"}
		hub.Register <- slow
		time.Sleep(10 * time.Millisecond)

		// Llenar buffer y forzar drop
		WSBroadcast(hub, []byte("1"))
		WSBroadcast(hub, []byte("2"))
		time.Sleep(20 * time.Millisecond)

		// Drenar Send: deberia tener el primer mensaje y luego estar cerrado
		got := []string{}
		for msg := range slow.Send {
			got = append(got, string(msg))
		}
		if len(got) > 1 {
			t.Errorf("expected at most 1 message in slow client, got %d: %v", len(got), got)
		}
	})
}

// --- WSSend ---

func TestWSSend(t *testing.T) {
	t.Run("envia mensaje al canal Send del cliente", func(t *testing.T) {
		client := &WSClient{Send: make(chan []byte, 4), ID: "c1"}
		err := WSSend(client, []byte("hola"))
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		got := <-client.Send
		if string(got) != "hola" {
			t.Errorf("got %q, want hola", string(got))
		}
	})

	t.Run("retorna error si client es nil", func(t *testing.T) {
		err := WSSend(nil, []byte("x"))
		if err == nil {
			t.Error("expected error for nil client, got nil")
		}
	})

	t.Run("retorna error si el canal esta lleno", func(t *testing.T) {
		client := &WSClient{Send: make(chan []byte, 1), ID: "c1"}
		_ = WSSend(client, []byte("1"))
		err := WSSend(client, []byte("2"))
		if err == nil {
			t.Error("expected error when send channel full, got nil")
		}
	})
}

// --- WSUpgrader & WSHandler integration ---

func TestWSUpgrader(t *testing.T) {
	t.Run("upgradea conexion valida con `*` en origenes", func(t *testing.T) {
		ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			conn, err := WSUpgrader(w, r, []string{"*"})
			if err != nil {
				return
			}
			defer conn.Close(websocket.StatusNormalClosure, "")
			// Echo
			_, data, _ := conn.Read(r.Context())
			conn.Write(r.Context(), websocket.MessageText, data)
		}))
		defer ts.Close()

		wsURL := "ws" + strings.TrimPrefix(ts.URL, "http")
		ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
		defer cancel()

		c, _, err := websocket.Dial(ctx, wsURL, nil)
		if err != nil {
			t.Fatalf("dial failed: %v", err)
		}
		defer c.Close(websocket.StatusNormalClosure, "")

		if err := c.Write(ctx, websocket.MessageText, []byte("ping")); err != nil {
			t.Fatalf("write failed: %v", err)
		}
		_, data, err := c.Read(ctx)
		if err != nil {
			t.Fatalf("read failed: %v", err)
		}
		if string(data) != "ping" {
			t.Errorf("got %q, want ping", string(data))
		}
	})
}

func TestWSHandler(t *testing.T) {
	t.Run("upgradea conexion y registra cliente en hub", func(t *testing.T) {
		hub := NewWSHub()
		go hub.Run()
		defer hub.Stop()

		ts := httptest.NewServer(WSHandler(hub, []string{"*"}))
		defer ts.Close()

		wsURL := "ws" + strings.TrimPrefix(ts.URL, "http")
		ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
		defer cancel()

		c, _, err := websocket.Dial(ctx, wsURL, nil)
		if err != nil {
			t.Fatalf("dial failed: %v", err)
		}
		defer c.Close(websocket.StatusNormalClosure, "")

		// Esperar a que el hub procese el Register
		time.Sleep(50 * time.Millisecond)

		if hub.ClientCount() != 1 {
			t.Errorf("expected 1 client in hub, got %d", hub.ClientCount())
		}
	})

	t.Run("broadcast del hub llega al cliente conectado", func(t *testing.T) {
		hub := NewWSHub()
		go hub.Run()
		defer hub.Stop()

		ts := httptest.NewServer(WSHandler(hub, []string{"*"}))
		defer ts.Close()

		wsURL := "ws" + strings.TrimPrefix(ts.URL, "http")
		ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
		defer cancel()

		c, _, err := websocket.Dial(ctx, wsURL, nil)
		if err != nil {
			t.Fatalf("dial failed: %v", err)
		}
		defer c.Close(websocket.StatusNormalClosure, "")

		time.Sleep(50 * time.Millisecond)

		WSBroadcast(hub, []byte("hello-all"))

		_, data, err := c.Read(ctx)
		if err != nil {
			t.Fatalf("read failed: %v", err)
		}
		if string(data) != "hello-all" {
			t.Errorf("got %q, want hello-all", string(data))
		}
	})

	t.Run("multiples clientes reciben el broadcast", func(t *testing.T) {
		hub := NewWSHub()
		go hub.Run()
		defer hub.Stop()

		ts := httptest.NewServer(WSHandler(hub, []string{"*"}))
		defer ts.Close()

		wsURL := "ws" + strings.TrimPrefix(ts.URL, "http")
		ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
		defer cancel()

		const N = 3
		conns := make([]*websocket.Conn, N)
		for i := 0; i < N; i++ {
			c, _, err := websocket.Dial(ctx, wsURL, nil)
			if err != nil {
				t.Fatalf("dial %d failed: %v", i, err)
			}
			conns[i] = c
			defer c.Close(websocket.StatusNormalClosure, "")
		}

		time.Sleep(80 * time.Millisecond)

		WSBroadcast(hub, []byte("multicast"))

		var wg sync.WaitGroup
		errs := make(chan error, N)
		for i := 0; i < N; i++ {
			wg.Add(1)
			go func(idx int, conn *websocket.Conn) {
				defer wg.Done()
				_, data, err := conn.Read(ctx)
				if err != nil {
					errs <- err
					return
				}
				if string(data) != "multicast" {
					errs <- &readMismatchError{idx: idx, got: string(data)}
				}
			}(i, conns[i])
		}
		wg.Wait()
		close(errs)
		for e := range errs {
			t.Errorf("client receive error: %v", e)
		}
	})

	t.Run("desregistra cliente al desconectar", func(t *testing.T) {
		hub := NewWSHub()
		go hub.Run()
		defer hub.Stop()

		ts := httptest.NewServer(WSHandler(hub, []string{"*"}))
		defer ts.Close()

		wsURL := "ws" + strings.TrimPrefix(ts.URL, "http")
		ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
		defer cancel()

		c, _, err := websocket.Dial(ctx, wsURL, nil)
		if err != nil {
			t.Fatalf("dial failed: %v", err)
		}

		time.Sleep(50 * time.Millisecond)
		if hub.ClientCount() != 1 {
			t.Fatalf("expected 1 client after dial, got %d", hub.ClientCount())
		}

		c.Close(websocket.StatusNormalClosure, "")
		time.Sleep(100 * time.Millisecond)

		if hub.ClientCount() != 0 {
			t.Errorf("expected 0 clients after close, got %d", hub.ClientCount())
		}
	})
}

type readMismatchError struct {
	idx int
	got string
}

func (e *readMismatchError) Error() string {
	return "client " + string(rune('0'+e.idx)) + " got " + e.got
}
