package rigctld

import (
	"fmt"
	"net"
	"strings"
	"sync"
	"testing"
	"time"
)

// A listener that binds successfully is not necessarily the one clients reach.
// Windows lets a second program bind the same port on the specific address
// 127.0.0.1 while ours holds 0.0.0.0, and localhost connections then go to the
// more specific one. Seen with Nexus, which starts its own rigctld on 4532.
func TestSelfTestWarnsWhenAnotherProgramHoldsLocalhost(t *testing.T) {
	// Squat 127.0.0.1 first, the way the other program does.
	squat, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		t.Skipf("cannot bind localhost here: %v", err)
	}
	defer squat.Close()
	port := squat.Addr().(*net.TCPAddr).Port
	go func() {
		for {
			c, err := squat.Accept()
			if err != nil {
				return
			}
			c.Close()
		}
	}()

	var mu sync.Mutex
	var lines []string
	s := New(port, nil, func(f string, a ...any) {
		mu.Lock()
		lines = append(lines, fmt.Sprintf(f, a...))
		mu.Unlock()
	})
	if err := s.Start(); err != nil {
		// The wildcard bind is refused on some setups; nothing to prove then.
		t.Skipf("wildcard bind refused: %v", err)
	}
	defer s.Stop()

	deadline := time.Now().Add(4 * time.Second)
	for time.Now().Before(deadline) {
		mu.Lock()
		got := strings.Join(lines, "\n")
		mu.Unlock()
		if strings.Contains(got, "another program is already answering") {
			return
		}
		time.Sleep(50 * time.Millisecond)
	}
	mu.Lock()
	defer mu.Unlock()
	t.Errorf("no warning was logged while another listener owned localhost:%d.\nlog was:\n%s",
		port, strings.Join(lines, "\n"))
}

// The healthy case must stay silent: a warning on every clean start would be
// noise, and noise in a log is what makes the real line easy to miss.
func TestSelfTestIsSilentWhenReachable(t *testing.T) {
	// A real, free port: with 0 the OS picks one but s.port stays 0, so the probe
	// would dial 127.0.0.1:0 and prove nothing.
	probe, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		t.Skipf("cannot bind: %v", err)
	}
	port := probe.Addr().(*net.TCPAddr).Port
	probe.Close()

	var mu sync.Mutex
	var lines []string
	s := New(port, nil, func(f string, a ...any) {
		mu.Lock()
		lines = append(lines, fmt.Sprintf(f, a...))
		mu.Unlock()
	})
	if err := s.Start(); err != nil {
		t.Skipf("start: %v", err)
	}
	defer s.Stop()
	time.Sleep(1500 * time.Millisecond)
	mu.Lock()
	defer mu.Unlock()
	for _, l := range lines {
		if strings.Contains(l, "WARNING") {
			t.Errorf("a reachable port still warned: %q", l)
		}
	}
}
