package loadsim

import (
	"context"
	"net"
	"sync/atomic"
	"testing"
	"time"
)

func TestRunnerDialsTargetConnectionsAndWritesFrames(t *testing.T) {
	var writes atomic.Int64
	var closed atomic.Int64
	runner := Runner{
		Dial: func(ctx context.Context, network, addr string) (net.Conn, error) {
			return &recordingConn{
				write: func(p []byte) (int, error) {
					writes.Add(1)
					return len(p), nil
				},
				close: func() error {
					closed.Add(1)
					return nil
				},
			}, nil
		},
	}

	stats, err := runner.Run(context.Background(), Config{
		Protocol:             ProtocolJT808,
		Addr:                 "127.0.0.1:808",
		Connections:          3,
		ConnectRatePerSecond: 1000,
		SendInterval:         time.Millisecond,
		Duration:             5 * time.Millisecond,
		Template:             "0200",
		SendFrames:           true,
	})
	if err != nil {
		t.Fatalf("Run() error = %v", err)
	}

	if stats.ConnectionsOpened != 3 {
		t.Fatalf("ConnectionsOpened = %d, want 3", stats.ConnectionsOpened)
	}
	if stats.FramesWritten == 0 {
		t.Fatalf("FramesWritten = 0, want at least one")
	}
	if writes.Load() != int64(stats.FramesWritten) {
		t.Fatalf("recorded writes = %d, stats = %d", writes.Load(), stats.FramesWritten)
	}
	if closed.Load() != 3 {
		t.Fatalf("closed = %d, want 3", closed.Load())
	}
}

func TestRunnerWritesVariantFrames(t *testing.T) {
	writes := make(chan []byte, 8)
	runner := Runner{
		Dial: func(ctx context.Context, network, addr string) (net.Conn, error) {
			return &recordingConn{
				write: func(p []byte) (int, error) {
					cp := append([]byte(nil), p...)
					select {
					case writes <- cp:
					default:
					}
					return len(p), nil
				},
				close: func() error { return nil },
			}, nil
		},
	}

	stats, err := runner.Run(context.Background(), Config{
		Protocol:             ProtocolJT808,
		Addr:                 "127.0.0.1:808",
		Connections:          1,
		ConnectRatePerSecond: 1000,
		SendInterval:         time.Millisecond,
		Duration:             6 * time.Millisecond,
		Template:             "0200",
		SendFrames:           true,
	})
	if err != nil {
		t.Fatalf("Run() error = %v", err)
	}
	if stats.FramesWritten < 2 {
		t.Fatalf("FramesWritten = %d, want at least 2", stats.FramesWritten)
	}

	first := <-writes
	second := <-writes
	if string(first) == string(second) {
		t.Fatal("runner wrote byte-identical frames; want per-frame variants")
	}
}

func TestRunnerCanHoldConnectionsWithoutWritingFrames(t *testing.T) {
	var writes atomic.Int64
	runner := Runner{
		Dial: func(ctx context.Context, network, addr string) (net.Conn, error) {
			return &recordingConn{
				write: func(p []byte) (int, error) {
					writes.Add(1)
					return len(p), nil
				},
				close: func() error { return nil },
			}, nil
		},
	}

	stats, err := runner.Run(context.Background(), Config{
		Protocol:             ProtocolJT808,
		Addr:                 "127.0.0.1:808",
		Connections:          2,
		ConnectRatePerSecond: 1000,
		SendInterval:         time.Millisecond,
		Duration:             5 * time.Millisecond,
		Template:             "0200",
		SendFrames:           false,
	})
	if err != nil {
		t.Fatalf("Run() error = %v", err)
	}
	if stats.ConnectionsOpened != 2 {
		t.Fatalf("ConnectionsOpened = %d, want 2", stats.ConnectionsOpened)
	}
	if stats.FramesWritten != 0 || writes.Load() != 0 {
		t.Fatalf("frames written stats=%d writes=%d, want 0", stats.FramesWritten, writes.Load())
	}
}

func TestRunnerDrainsProtocolResponses(t *testing.T) {
	runner := Runner{
		Dial: func(ctx context.Context, network, addr string) (net.Conn, error) {
			client, server := net.Pipe()
			go func() {
				defer server.Close()
				buffer := make([]byte, 4096)
				for {
					if _, err := server.Read(buffer); err != nil {
						return
					}
					if _, err := server.Write([]byte{0x7e, 0x80, 0x01, 0x7e}); err != nil {
						return
					}
				}
			}()
			return client, nil
		},
	}

	stats, err := runner.Run(context.Background(), Config{
		Protocol:             ProtocolJT808,
		Addr:                 "127.0.0.1:808",
		Connections:          1,
		ConnectRatePerSecond: 1000,
		SendInterval:         time.Millisecond,
		Duration:             10 * time.Millisecond,
		Template:             "0200",
		SendFrames:           true,
		DrainResponses:       true,
	})
	if err != nil {
		t.Fatalf("Run() error = %v", err)
	}
	if stats.ResponseBytes == 0 {
		t.Fatalf("ResponseBytes = 0, want drained protocol responses")
	}
	if stats.ReadErrors != 0 {
		t.Fatalf("ReadErrors = %d, want 0", stats.ReadErrors)
	}
}

type recordingConn struct {
	write func([]byte) (int, error)
	close func() error
}

func (c *recordingConn) Read(p []byte) (int, error)         { return 0, context.Canceled }
func (c *recordingConn) Write(p []byte) (int, error)        { return c.write(p) }
func (c *recordingConn) Close() error                       { return c.close() }
func (c *recordingConn) LocalAddr() net.Addr                { return testAddr("local") }
func (c *recordingConn) RemoteAddr() net.Addr               { return testAddr("remote") }
func (c *recordingConn) SetDeadline(t time.Time) error      { return nil }
func (c *recordingConn) SetReadDeadline(t time.Time) error  { return nil }
func (c *recordingConn) SetWriteDeadline(t time.Time) error { return nil }

type testAddr string

func (a testAddr) Network() string { return "tcp" }
func (a testAddr) String() string  { return string(a) }
