package tcp

import (
	"testing"
)

func TestPopCount(t *testing.T) {
	tests := []struct {
		input byte
		want  int
	}{
		{0x00, 0},
		{0x01, 1},
		{0x02, 1},
		{0x03, 2},
		{0x07, 3},
		{0x0f, 4},
		{0xff, 8},
		{0x55, 4}, // 01010101
		{0xaa, 4}, // 10101010
	}

	for _, tt := range tests {
		if got := popCount(tt.input); got != tt.want {
			t.Errorf("popCount(0x%02x) = %d, want %d", tt.input, got, tt.want)
		}
	}
}

func TestAveragePopCount(t *testing.T) {
	tests := []struct {
		name  string
		input []byte
		want  float32
	}{
		{"empty", []byte{}, 0},
		{"all zeros", []byte{0x00, 0x00, 0x00}, 0},
		{"all ones", []byte{0xff, 0xff}, 8.0},
		{"mixed", []byte{0x00, 0xff}, 4.0},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := averagePopCount(tt.input)
			if got != tt.want {
				t.Errorf("averagePopCount() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestIsFirstSixPrintable(t *testing.T) {
	tests := []struct {
		name  string
		input []byte
		want  bool
	}{
		{"too short", []byte("abc"), false},
		{"all printable", []byte("abcdef"), true},
		{"non-printable at pos 0", []byte{0x00, 'a', 'b', 'c', 'd', 'e'}, false},
		{"non-printable at pos 5", []byte{'a', 'b', 'c', 'd', 'e', 0x1f}, false},
		{"exactly 6 printable", []byte("123456"), true},
		{"spaces", []byte("      "), true},
		{"non-printable middle", []byte{'a', 'b', 0x01, 'd', 'e', 'f'}, false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := isFirstSixPrintable(tt.input)
			if got != tt.want {
				t.Errorf("isFirstSixPrintable(%q) = %v, want %v", tt.input, got, tt.want)
			}
		})
	}
}

func TestPrintablePercentage(t *testing.T) {
	tests := []struct {
		name  string
		input []byte
		want  float32
	}{
		{"empty", []byte{}, 0},
		{"all printable", []byte("hello"), 1.0},
		{"none printable", []byte{0x00, 0x01, 0x02, 0x03}, 0},
		{"half printable", []byte{'a', 0x00, 'b', 0x00}, 0.5},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := printablePercentage(tt.input)
			if got != tt.want {
				t.Errorf("printablePercentage() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestContiguousPrintable(t *testing.T) {
	tests := []struct {
		name  string
		input []byte
		want  int
	}{
		{"empty", []byte{}, 0},
		{"all printable", []byte("hello world"), 11},
		{"none printable", []byte{0x00, 0x01, 0x02}, 0},
		{"start printable", []byte{'a', 'b', 'c', 0x00, 'd', 'e', 'f'}, 3},
		{"end printable", []byte{0x00, 'a', 'b', 'c', 'd', 'e', 'f'}, 6},
		{"middle printable", []byte{0x00, 'a', 'b', 'c', 0x00}, 3},
		{"two segments", []byte{'a', 'b', 0x00, 'c', 'd', 'e'}, 3},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := contiguousPrintable(tt.input)
			if got != tt.want {
				t.Errorf("contiguousPrintable() = %d, want %d", got, tt.want)
			}
		})
	}
}

func TestIsTLSorHTTP(t *testing.T) {
	tests := []struct {
		name  string
		input []byte
		want  bool
	}{
		{"too short", []byte("AB"), false},
		// TLS ClientHello: 0x16 0x03 0x01
		{"tls 1.0", []byte{0x16, 0x03, 0x01}, true},
		// TLS 0x17 is application data record
		{"tls app data", []byte{0x17, 0x03, 0x03}, true},
		{"tls max content type", []byte{0x17, 0x03, 0x09}, true},
		{"bad tls content type", []byte{0x15, 0x03, 0x01}, false},
		{"bad tls version", []byte{0x16, 0x04, 0x01}, false},
		{"bad tls length", []byte{0x16, 0x03, 0x0a}, false},
		// HTTP methods
		{"GET", []byte("GET / HTTP/1.1..."), true},
		{"HEAD", []byte("HEAD /index.html..."), true},
		{"POST", []byte("POST /api..."), true},
		{"PUT", []byte("PUT /data..."), true},
		{"DELETE", []byte("DELETE /..."), true},
		{"CONNECT", []byte("CONNECT proxy..."), true},
		{"OPTIONS", []byte("OPTIONS *..."), true},
		{"TRACE", []byte("TRACE /..."), true},
		{"PATCH", []byte("PATCH /..."), true},
		{"random data", []byte{0x00, 0x01, 0x02, 0x03}, false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := isTLSorHTTP(tt.input)
			if got != tt.want {
				t.Errorf("isTLSorHTTP(%v) = %v, want %v", tt.input, got, tt.want)
			}
		})
	}
}

func TestIsPrintable(t *testing.T) {
	if !isPrintable('a') {
		t.Error("'a' should be printable")
	}
	if isPrintable(0x00) {
		t.Error("0x00 should not be printable")
	}
	if !isPrintable(0x20) {
		t.Error("0x20 (space) should be printable")
	}
	if !isPrintable(0x7e) {
		t.Error("0x7e (~) should be printable")
	}
	if isPrintable(0x7f) {
		t.Error("0x7f (DEL) should not be printable")
	}
}

func TestFETStream_Feed(t *testing.T) {
	s := newFETStream(nil)
	u, done := s.Feed(false, false, false, 0, []byte("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"))
	if u == nil {
		t.Fatal("Feed returned nil update")
	}
	if !done {
		t.Error("FET should be done after first packet")
	}
	m := u.M
	if _, ok := m["ex1"]; !ok {
		t.Error("ex1 missing")
	}
	if _, ok := m["ex2"]; !ok {
		t.Error("ex2 missing")
	}
	if _, ok := m["ex3"]; !ok {
		t.Error("ex3 missing")
	}
	if _, ok := m["ex4"]; !ok {
		t.Error("ex4 missing")
	}
	if _, ok := m["ex5"]; !ok {
		t.Error("ex5 missing")
	}
	if yes, ok := m["yes"].(bool); ok && yes {
		t.Error("HTTP should be exempt (yes=false)")
	}
	if u.Type != 2 {
		t.Errorf("prop update type = %d, want PropUpdateReplace", u.Type)
	}
}

func TestFETStream_Feed_EncryptedLike(t *testing.T) {
	s := newFETStream(nil)
	data := make([]byte, 100)
	for i := range data {
		data[i] = byte(i % 256)
	}
	u, done := s.Feed(false, false, false, 0, data)
	if u == nil {
		t.Fatal("Feed returned nil update")
	}
	if !done {
		t.Error("should be done")
	}
}

func TestFETStream_Feed_Skip(t *testing.T) {
	s := newFETStream(nil)
	_, done := s.Feed(false, false, false, 5, []byte("data"))
	if !done {
		t.Error("skip != 0 should return done=true")
	}
}

func TestFETStream_Feed_Empty(t *testing.T) {
	s := newFETStream(nil)
	u, done := s.Feed(false, false, false, 0, []byte{})
	if u != nil || done {
		t.Error("empty data should return nil, false")
	}
}

func TestFETAnalyzer_Name(t *testing.T) {
	a := &FETAnalyzer{}
	if a.Name() != "fet" {
		t.Errorf("Name() = %q, want fet", a.Name())
	}
}

func TestFETStream_Close(t *testing.T) {
	s := newFETStream(nil)
	if u := s.Close(false); u != nil {
		t.Error("Close should return nil")
	}
}
