package rcon

import (
	"bufio"
	"context"
	"fmt"
	"net"
	"strings"
	"testing"
	"time"
)

// fakeTelnetServer speaks enough of the 7DTD protocol to exercise dial+auth+exec:
//   - sends "Please enter password:" on accept
//   - waits for client password (any line), then sends "Logon successful.\n"
//   - for each subsequent command line, responds with its registered output
type fakeTelnetServer struct {
	addr      string
	responses map[string]string
}

func startFakeTelnet(t *testing.T, responses map[string]string) *fakeTelnetServer {
	t.Helper()
	ln, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		t.Fatalf("listen: %v", err)
	}
	t.Cleanup(func() { _ = ln.Close() })

	fs := &fakeTelnetServer{addr: ln.Addr().String(), responses: responses}
	go func() {
		for {
			conn, err := ln.Accept()
			if err != nil {
				return
			}
			go fs.handle(conn)
		}
	}()
	return fs
}

func (fs *fakeTelnetServer) handle(conn net.Conn) {
	defer conn.Close()
	_, _ = conn.Write([]byte("*** Connected with 7DTD server.\r\nPlease enter password:"))

	br := bufio.NewReader(conn)
	if _, err := br.ReadString('\n'); err != nil {
		return
	}
	_, _ = conn.Write([]byte("\nLogon successful.\n"))

	for {
		_ = conn.SetReadDeadline(time.Now().Add(5 * time.Second))
		line, err := br.ReadString('\n')
		if err != nil {
			return
		}
		cmd := strings.TrimSpace(line)
		resp, ok := fs.responses[cmd]
		if !ok {
			resp = fmt.Sprintf("(unknown command: %s)\n", cmd)
		}
		_, _ = conn.Write([]byte(resp))
	}
}

func TestTelnetDialAndExec(t *testing.T) {
	srv := startFakeTelnet(t, map[string]string{
		"lp":  "Total of 3 in the game\n\n1. id=1, Alice, pos=(0,0,0)\n2. id=2, Bob, pos=(1,0,0)\n3. id=3, Carol, pos=(2,0,0)\n\n",
		"say \"hello\"": "*Server: hello\n",
	})

	host, port := splitAddr(t, srv.addr)

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	client, err := TelnetDialer{}.Dial(ctx, DialOptions{
		Host:           host,
		Port:           port,
		Password:       "test",
		ConnectTimeout: 2 * time.Second,
		ReadIdle:       150 * time.Millisecond,
	})
	if err != nil {
		t.Fatalf("Dial: %v", err)
	}
	defer client.Close()

	out, err := client.Exec(ctx, "lp")
	if err != nil {
		t.Fatalf("Exec lp: %v", err)
	}
	if !strings.Contains(out, "Total of 3 in the game") {
		t.Errorf("lp output missing expected substring, got: %q", out)
	}

	out, err = client.Exec(ctx, `say "hello"`)
	if err != nil {
		t.Fatalf(`Exec say: %v`, err)
	}
	if !strings.Contains(out, "*Server: hello") {
		t.Errorf("say output missing, got: %q", out)
	}
}

func TestDialerForUnknown(t *testing.T) {
	if _, err := DialerFor("source_rcon"); err == nil {
		t.Error("expected error for unknown adapter")
	}
	if d, err := DialerFor("telnet"); err != nil || d.Name() != "telnet" {
		t.Errorf("DialerFor(telnet): d=%v err=%v", d, err)
	}
}

func splitAddr(t *testing.T, addr string) (string, int) {
	t.Helper()
	host, portStr, err := net.SplitHostPort(addr)
	if err != nil {
		t.Fatalf("split %q: %v", addr, err)
	}
	var port int
	if _, err := fmt.Sscanf(portStr, "%d", &port); err != nil {
		t.Fatalf("port parse: %v", err)
	}
	return host, port
}
