package sniproxy

import (
	"sync"
	"testing"
)

func TestRouter_pick_exact_match(t *testing.T) {
	fb := Backend{Name: "fallback", Addr: "127.0.0.1:9000"}
	r := NewRouter(fb)
	r.Replace([]Route{
		{Match: "turn.example.com", Backend: Backend{Name: "turn", Addr: "127.0.0.1:5349"}},
	}, fb)

	got := r.Pick("turn.example.com")
	if got.Addr != "127.0.0.1:5349" {
		t.Errorf("expected turn backend, got %+v", got)
	}
}

func TestRouter_pick_unmatched_returns_fallback(t *testing.T) {
	fb := Backend{Name: "caddy", Addr: "127.0.0.1:8443"}
	r := NewRouter(fb)
	r.Replace([]Route{
		{Match: "turn.example.com", Backend: Backend{Addr: "127.0.0.1:5349"}},
	}, fb)

	if got := r.Pick("api.example.com"); got != fb {
		t.Errorf("expected fallback, got %+v", got)
	}
	if got := r.Pick(""); got != fb {
		t.Errorf("expected fallback for empty SNI, got %+v", got)
	}
}

func TestRouter_pick_case_insensitive(t *testing.T) {
	fb := Backend{Addr: "127.0.0.1:8443"}
	r := NewRouter(fb)
	r.Replace([]Route{
		{Match: "Turn.Example.Com", Backend: Backend{Addr: "127.0.0.1:5349"}},
	}, fb)

	if got := r.Pick("turn.example.com"); got.Addr != "127.0.0.1:5349" {
		t.Errorf("expected case-insensitive match, got %+v", got)
	}
}

func TestRouter_pick_wildcard_subdomain(t *testing.T) {
	fb := Backend{Addr: "127.0.0.1:8443"}
	r := NewRouter(fb)
	r.Replace([]Route{
		{Match: "*.example.com", Backend: Backend{Name: "wild", Addr: "127.0.0.1:5349"}},
	}, fb)

	cases := map[string]bool{
		"a.example.com":    true,
		"foo.example.com":  true,
		"a.b.example.com":  false, // multi-label not allowed
		"example.com":      false, // bare domain doesn't match *.example.com
		"other.com":        false,
	}
	for sni, want := range cases {
		got := r.Pick(sni) == Backend{Name: "wild", Addr: "127.0.0.1:5349"}
		if got != want {
			t.Errorf("Pick(%q): want match=%v, got match=%v", sni, want, got)
		}
	}
}

func TestRouter_replace_atomic(t *testing.T) {
	// Many concurrent reads against many concurrent Replace calls — should
	// never observe partial state. Run with -race.
	fb := Backend{Addr: "fb"}
	r := NewRouter(fb)
	r.Replace([]Route{{Match: "a.com", Backend: Backend{Addr: "1"}}}, fb)

	var wg sync.WaitGroup
	stop := make(chan struct{})

	// Readers
	for i := 0; i < 4; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			for {
				select {
				case <-stop:
					return
				default:
					_ = r.Pick("a.com")
				}
			}
		}()
	}

	// Writers
	for i := 0; i < 200; i++ {
		r.Replace([]Route{{Match: "a.com", Backend: Backend{Addr: "x"}}}, fb)
	}
	close(stop)
	wg.Wait()
}

func TestRouter_routes_returns_copy(t *testing.T) {
	r := NewRouter(Backend{})
	original := []Route{{Match: "a", Backend: Backend{Addr: "1"}}}
	r.Replace(original, Backend{})
	got := r.Routes()
	got[0].Match = "mutated"
	if r.Routes()[0].Match != "a" {
		t.Error("Routes() should return a defensive copy")
	}
}
