package parser_test

import (
	"encoding/hex"
	"testing"

	"github.com/antitbone/ja4/ja4ebpf/internal/parser"
)

func TestParseClientHelloMinimal(t *testing.T) {
	// Construit un ClientHello minimal valide manuellement
	// pour tester le parsing sans dépendre d'un vrai paquet capturé.
	raw := buildMinimalClientHello()

	ch, err := parser.ParseClientHello(raw)
	if err != nil {
		t.Fatalf("ParseClientHello a échoué: %v", err)
	}

	if ch.HandshakeVersion != 0x0303 {
		t.Errorf("version attendue 0x0303, obtenue 0x%04x", ch.HandshakeVersion)
	}
	if len(ch.CipherSuites) == 0 {
		t.Error("aucune cipher suite extraite")
	}
}

func TestIsGREASEFiltering(t *testing.T) {
	// Les valeurs GREASE doivent être filtrées dans ComputeJA4
	raw := buildClientHelloWithGREASE()
	ch, err := parser.ParseClientHello(raw)
	if err != nil {
		t.Fatalf("ParseClientHello: %v", err)
	}

	ja4 := parser.ComputeJA4(ch)
	if ja4 == "" {
		t.Error("ComputeJA4 a retourné une chaîne vide")
	}
	t.Logf("JA4 = %s", ja4)
}

func TestComputeJA4Format(t *testing.T) {
	raw := buildMinimalClientHello()
	ch, err := parser.ParseClientHello(raw)
	if err != nil {
		t.Fatalf("ParseClientHello: %v", err)
	}

	ja4 := parser.ComputeJA4(ch)
	// Format attendu : t{ver}{sni}{cc}{ec}_{12hex}_{12hex}
	// 5 chars + "_" + 12 chars + "_" + 12 chars = 31 chars minimum
	if len(ja4) < 31 {
		t.Errorf("longueur JA4 inattendue: %d chars — valeur: %q", len(ja4), ja4)
	}
	// Le premier caractère doit être 't' (TCP)
	if ja4[0] != 't' {
		t.Errorf("JA4 doit commencer par 't', obtenu %q", string(ja4[0]))
	}
}

func TestSNIExtraction(t *testing.T) {
	raw := buildClientHelloWithSNI("example.com")
	ch, err := parser.ParseClientHello(raw)
	if err != nil {
		t.Fatalf("ParseClientHello: %v", err)
	}
	if ch.SNI != "example.com" {
		t.Errorf("SNI attendu %q, obtenu %q", "example.com", ch.SNI)
	}

	ja4 := parser.ComputeJA4(ch)
	// SNI présent → flag 'd' en position 3
	if len(ja4) >= 3 && ja4[3] != 'd' {
		t.Errorf("flag SNI attendu 'd', obtenu %q dans JA4=%q", string(ja4[3]), ja4)
	}
}

func TestParseClientHelloTooShort(t *testing.T) {
	_, err := parser.ParseClientHello([]byte{0x16, 0x03, 0x01})
	if err == nil {
		t.Error("attendu une erreur pour un payload trop court")
	}
}

func TestParseClientHelloWrongType(t *testing.T) {
	// Type 0x17 = Application Data, pas un Handshake
	raw := make([]byte, 20)
	raw[0] = 0x17
	_, err := parser.ParseClientHello(raw)
	if err == nil {
		t.Error("attendu une erreur pour un Record Type incorrect")
	}
}

// ── Helpers de construction de paquets TLS de test ────────────────────────

// buildMinimalClientHello construit un ClientHello TLS 1.2 minimal valide.
func buildMinimalClientHello() []byte {
	// Contenu du Handshake (sans l'en-tête Record Layer)
	var hs []byte

	// Version ClientHello : TLS 1.2
	hs = append(hs, 0x03, 0x03)

	// Random : 32 octets
	random := make([]byte, 32)
	hs = append(hs, random...)

	// Session ID : longueur 0
	hs = append(hs, 0x00)

	// Cipher Suites : 2 suites (TLS_AES_128_GCM_SHA256, TLS_RSA_WITH_AES_128_CBC_SHA)
	hs = append(hs, 0x00, 0x04) // longueur = 4 octets
	hs = append(hs, 0x13, 0x01) // TLS_AES_128_GCM_SHA256
	hs = append(hs, 0x00, 0x2f) // TLS_RSA_WITH_AES_128_CBC_SHA

	// Compression Methods : 1 méthode (null)
	hs = append(hs, 0x01, 0x00)

	// Pas d'extensions
	hs = append(hs, 0x00, 0x00)

	return buildTLSRecord(hs)
}

// buildClientHelloWithGREASE ajoute des valeurs GREASE aux cipher suites.
func buildClientHelloWithGREASE() []byte {
	var hs []byte
	hs = append(hs, 0x03, 0x03) // version
	hs = append(hs, make([]byte, 32)...) // random
	hs = append(hs, 0x00)         // session id len

	// Cipher suites avec GREASE (0x0a0a)
	hs = append(hs, 0x00, 0x06) // longueur = 6
	hs = append(hs, 0x0a, 0x0a) // GREASE (doit être filtré)
	hs = append(hs, 0x13, 0x01) // TLS_AES_128_GCM_SHA256
	hs = append(hs, 0x00, 0x2f) // TLS_RSA_WITH_AES_128_CBC_SHA

	hs = append(hs, 0x01, 0x00) // compression
	hs = append(hs, 0x00, 0x00) // no extensions

	return buildTLSRecord(hs)
}

// buildClientHelloWithSNI construit un ClientHello avec l'extension SNI.
func buildClientHelloWithSNI(hostname string) []byte {
	var hs []byte
	hs = append(hs, 0x03, 0x03)
	hs = append(hs, make([]byte, 32)...)
	hs = append(hs, 0x00)
	hs = append(hs, 0x00, 0x04)
	hs = append(hs, 0x13, 0x01, 0x00, 0x2f)
	hs = append(hs, 0x01, 0x00)

	// Extension SNI
	nameBytes := []byte(hostname)
	sniExt := buildSNIExtension(nameBytes)

	// Bloc d'extensions
	extBlock := sniExt
	hs = appendUint16(hs, uint16(len(extBlock)))
	hs = append(hs, extBlock...)

	return buildTLSRecord(hs)
}

func buildSNIExtension(hostname []byte) []byte {
	// Type : 0x0000 (SNI)
	// Longueur extension = 2 (liste len) + 1 (type) + 2 (name len) + len(hostname)
	nameLen := len(hostname)
	listLen := 1 + 2 + nameLen

	var ext []byte
	ext = append(ext, 0x00, 0x00)                              // type SNI
	ext = appendUint16(ext, uint16(2+listLen))                 // longueur extension
	ext = appendUint16(ext, uint16(listLen))                   // longueur liste SNI
	ext = append(ext, 0x00)                                    // type : host_name
	ext = appendUint16(ext, uint16(nameLen))                   // longueur hostname
	ext = append(ext, hostname...)
	return ext
}

// buildTLSRecord encapsule un message Handshake dans un Record Layer TLS.
func buildTLSRecord(handshakeBody []byte) []byte {
	hsLen := len(handshakeBody)
	var rec []byte

	// Record Layer
	rec = append(rec, 0x16)         // ContentType : Handshake
	rec = append(rec, 0x03, 0x01)   // Version : TLS 1.0
	rec = appendUint16(rec, uint16(4+hsLen)) // longueur = type(1) + len(3) + body

	// En-tête Handshake
	rec = append(rec, 0x01) // HandshakeType : ClientHello
	// Longueur sur 3 octets
	rec = append(rec, byte(hsLen>>16), byte(hsLen>>8), byte(hsLen))
	rec = append(rec, handshakeBody...)

	return rec
}

func appendUint16(b []byte, v uint16) []byte {
	return append(b, byte(v>>8), byte(v))
}

// TestParseClientHelloHex teste le parsing d'un hex dump (smoke test).
func TestParseClientHelloHexDump(t *testing.T) {
	// Hex dump minimal connu valide
	hexStr := "160301002f" + // Record : type=22, ver=0x0301, len=47
		"01000002b" + // Handshake : type=1, len=43 (ajusté)
		"0303" +
		"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" +
		"00" +  // session id len = 0
		"0004" + "13010035" + // cipher suites : 2 suites
		"0100" + // compression
		"0000" // no extensions

	raw, err := hex.DecodeString(
		"1603010032" + // Record Layer : type=22, len=50
			"01" + "00002e" + // HandshakeType=1, len=46
			"0303" + // version
			"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" + // random
			"00" + // session id len
			"0004" + "13010035" + // 2 cipher suites
			"01" + "00" + // compression
			"0000", // extensions
	)
	if err != nil {
		t.Skipf("hex decode: %v", err)
	}

	ch, err := parser.ParseClientHello(raw)
	if err != nil {
		// Acceptable : le hex dump est peut-être mal formé (longueurs)
		t.Logf("ParseClientHello (hex dump): %v", err)
		return
	}
	t.Logf("ClientHello parsé : version=0x%04x, ciphers=%d", ch.HandshakeVersion, len(ch.CipherSuites))
	_ = hexStr
}
