package cluster

import (
	"bufio"
	"net"
	"strconv"
	"strings"
	"testing"
	"time"
)

// A node whose whole greeting is a bare "login:" — no newline, no banner — and
// which then demands a password must still log in.
//
// Reported on f5mzn.org:9000: telnet by hand worked, OpsLog sat at "connecting"
// for ever. Two faults met there. The prompts carry no newline, and the read
// loop only ever looked at complete LINES, so neither prompt was seen at all;
// and the blind 1.5 s login never recorded that it had sent the callsign, which
// left the password branch — gated on that flag — permanently switched off.
//
// The test speaks the node's side literally: "login:" with no newline, then
// "password:" with no newline, then a spot. It asserts both answers arrive and
// that the session reaches Connected without any welcome banner to lean on.
func TestBareLoginAndPasswordPromptsWithoutNewlines(t *testing.T) {
	ln, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		t.Fatal(err)
	}
	defer ln.Close()

	host, portStr, _ := net.SplitHostPort(ln.Addr().String())
	port, err := strconv.Atoi(portStr)
	if err != nil {
		t.Fatal(err)
	}

	type answers struct{ login, pwd string }
	got := make(chan answers, 1)
	go func() {
		c, err := ln.Accept()
		if err != nil {
			return
		}
		defer c.Close()
		rd := bufio.NewReader(c)
		// No newline, exactly as the node sends it.
		if _, err := c.Write([]byte("login: ")); err != nil {
			return
		}
		call, err := rd.ReadString('\n')
		if err != nil {
			return
		}
		if _, err := c.Write([]byte("password: ")); err != nil {
			return
		}
		pwd, err := rd.ReadString('\n')
		if err != nil {
			return
		}
		got <- answers{strings.TrimSpace(call), strings.TrimSpace(pwd)}
		// Something to prove the link is live and parsing again afterwards.
		_, _ = c.Write([]byte("DX de F4BPO:     14074.0  OY1CT        FT8            1234Z\r\n"))
		time.Sleep(time.Second)
	}()

	spots := make(chan Spot, 4)
	s := &session{
		cfg:      ServerConfig{Name: "pwd node", Host: host, Port: port, Password: "s3cret"},
		login:    "F4BPO",
		onSpot:   func(sp Spot) { spots <- sp },
		onLine:   func(Line) {},
		onStatus: func() {},
		stopCh:   make(chan struct{}),
	}
	done := make(chan error, 1)
	go func() { _, err := s.runOnce(); done <- err }()
	defer func() { close(s.stopCh); <-done }()

	select {
	case a := <-got:
		if a.login != "F4BPO" {
			t.Errorf("callsign sent = %q, want F4BPO", a.login)
		}
		if a.pwd != "s3cret" {
			t.Errorf("password sent = %q, want s3cret — the prompt carried no newline", a.pwd)
		}
	case <-time.After(10 * time.Second):
		t.Fatal("the node's newline-less prompts were never answered")
	}

	select {
	case sp := <-spots:
		if sp.DXCall != "OY1CT" {
			t.Errorf("spot from the wrong station: %+v", sp)
		}
	case <-time.After(5 * time.Second):
		t.Fatal("no spot after the login — the stream is not being parsed")
	}

	// Connected without a welcome banner: the handshake alone must be enough.
	if st := s.snapshot().State; st != StateConnected {
		t.Errorf("state = %q, want %q — the server would still show as connecting", st, StateConnected)
	}
}
