package api

import (
	"testing"
)

func TestNewLogRecord(t *testing.T) {
	synToCHMs := uint32(150)
	tests := []struct {
		name        string
		clientHello TLSClientHello
		fingerprints *Fingerprints
		wantNil     bool
	}{
		{
			name: "complete record with fingerprints",
			clientHello: TLSClientHello{
				SrcIP:   "192.168.1.100",
				SrcPort: 54321,
				DstIP:   "10.0.0.1",
				DstPort: 443,
				ConnID:  "flow-abc123",
				SNI:     "example.com",
				ALPN:    "h2",
				TLSVersion: "1.3",
				SynToCHMs: &synToCHMs,
				IPMeta: IPMeta{
					TTL:         64,
					TotalLength: 512,
					IPID:        12345,
					DF:          true,
				},
				TCPMeta: TCPMeta{
					WindowSize:  65535,
					MSS:         1460,
					WindowScale: 7,
					Options:     []string{"MSS", "WS", "SACK", "TS"},
				},
			},
			fingerprints: &Fingerprints{
				JA4:     "t13d1516h2_8daaf6152771_02cb136f2775",
				JA4Hash: "8daaf6152771_02cb136f2775", // Internal use only
				JA3:     "771,4865-4866-4867,0-23-65281-10-11-35-16-5-13-18-51-45-43-27-17513,29-23-24,0",
				JA3Hash: "a0e6f06c7a6d15e5e3f0f0e6f06c7a6d",
			},
			wantNil: false,
		},
		{
			name: "record without fingerprints",
			clientHello: TLSClientHello{
				SrcIP:   "192.168.1.100",
				SrcPort: 54321,
				DstIP:   "10.0.0.1",
				DstPort: 443,
				ConnID:  "flow-xyz789",
				SNI:     "test.example.com",
				ALPN:    "http/1.1",
				TLSVersion: "1.2",
				IPMeta: IPMeta{
					TTL:         64,
					TotalLength: 512,
					IPID:        12345,
					DF:          true,
				},
				TCPMeta: TCPMeta{
					WindowSize:  65535,
					MSS:         1460,
					WindowScale: 7,
					Options:     []string{"MSS", "WS"},
				},
			},
			fingerprints: nil,
			wantNil:      false,
		},
		{
			name: "record with zero values for optional fields",
			clientHello: TLSClientHello{
				SrcIP:   "192.168.1.100",
				SrcPort: 54321,
				DstIP:   "10.0.0.1",
				DstPort: 443,
				IPMeta: IPMeta{
					TTL:         0,
					TotalLength: 0,
					IPID:        0,
					DF:          false,
				},
				TCPMeta: TCPMeta{
					WindowSize:  0,
					MSS:         0,
					WindowScale: 0,
					Options:     []string{},
				},
			},
			fingerprints: nil,
			wantNil:      false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			rec := NewLogRecord(tt.clientHello, tt.fingerprints)

			// Verify timestamp is set
			if rec.Timestamp == 0 {
				t.Error("Timestamp should be set")
			}

			// Verify basic fields
			if rec.SrcIP != tt.clientHello.SrcIP {
				t.Errorf("SrcIP = %v, want %v", rec.SrcIP, tt.clientHello.SrcIP)
			}
			if rec.SrcPort != tt.clientHello.SrcPort {
				t.Errorf("SrcPort = %v, want %v", rec.SrcPort, tt.clientHello.SrcPort)
			}
			if rec.DstIP != tt.clientHello.DstIP {
				t.Errorf("DstIP = %v, want %v", rec.DstIP, tt.clientHello.DstIP)
			}
			if rec.DstPort != tt.clientHello.DstPort {
				t.Errorf("DstPort = %v, want %v", rec.DstPort, tt.clientHello.DstPort)
			}

			// Verify IPMeta fields
			if rec.IPTTL != tt.clientHello.IPMeta.TTL {
				t.Errorf("IPTTL = %v, want %v", rec.IPTTL, tt.clientHello.IPMeta.TTL)
			}
			if rec.IPTotalLen != tt.clientHello.IPMeta.TotalLength {
				t.Errorf("IPTotalLen = %v, want %v", rec.IPTotalLen, tt.clientHello.IPMeta.TotalLength)
			}
			if rec.IPID != tt.clientHello.IPMeta.IPID {
				t.Errorf("IPID = %v, want %v", rec.IPID, tt.clientHello.IPMeta.IPID)
			}
			if rec.IPDF != tt.clientHello.IPMeta.DF {
				t.Errorf("IPDF = %v, want %v", rec.IPDF, tt.clientHello.IPMeta.DF)
			}

			// Verify TCPMeta fields
			if rec.TCPWindow != tt.clientHello.TCPMeta.WindowSize {
				t.Errorf("TCPWindow = %v, want %v", rec.TCPWindow, tt.clientHello.TCPMeta.WindowSize)
			}

			// Verify optional fields (MSS, WindowScale)
			if tt.clientHello.TCPMeta.MSS != 0 {
				if rec.TCPMSS == nil {
					t.Error("TCPMSS should not be nil when MSS != 0")
				} else if *rec.TCPMSS != tt.clientHello.TCPMeta.MSS {
					t.Errorf("TCPMSS = %v, want %v", *rec.TCPMSS, tt.clientHello.TCPMeta.MSS)
				}
			} else {
				if rec.TCPMSS != nil {
					t.Error("TCPMSS should be nil when MSS == 0")
				}
			}

			if tt.clientHello.TCPMeta.WindowScale != 0 {
				if rec.TCPWScale == nil {
					t.Error("TCPWScale should not be nil when WindowScale != 0")
				} else if *rec.TCPWScale != tt.clientHello.TCPMeta.WindowScale {
					t.Errorf("TCPWScale = %v, want %v", *rec.TCPWScale, tt.clientHello.TCPMeta.WindowScale)
				}
			} else {
				if rec.TCPWScale != nil {
					t.Error("TCPWScale should be nil when WindowScale == 0")
				}
			}

			// Verify new TLS fields
			if rec.ConnID != tt.clientHello.ConnID {
				t.Errorf("ConnID = %v, want %v", rec.ConnID, tt.clientHello.ConnID)
			}
			if rec.SNI != tt.clientHello.SNI {
				t.Errorf("SNI = %v, want %v", rec.SNI, tt.clientHello.SNI)
			}
			if rec.ALPN != tt.clientHello.ALPN {
				t.Errorf("ALPN = %v, want %v", rec.ALPN, tt.clientHello.ALPN)
			}
			if rec.TLSVersion != tt.clientHello.TLSVersion {
				t.Errorf("TLSVersion = %v, want %v", rec.TLSVersion, tt.clientHello.TLSVersion)
			}
			if tt.clientHello.SynToCHMs != nil {
				if rec.SynToCHMs == nil {
					t.Error("SynToCHMs should not be nil")
				} else if *rec.SynToCHMs != *tt.clientHello.SynToCHMs {
					t.Errorf("SynToCHMs = %v, want %v", *rec.SynToCHMs, *tt.clientHello.SynToCHMs)
				}
			}

			// Verify fingerprints (note: JA4Hash is NOT in LogRecord per architecture)
			if tt.fingerprints != nil {
				if rec.JA4 != tt.fingerprints.JA4 {
					t.Errorf("JA4 = %v, want %v", rec.JA4, tt.fingerprints.JA4)
				}
				// JA4Hash is intentionally NOT in LogRecord (architecture decision)
				// JA3Hash is still present as it's the MD5 of JA3 (needed for exploitation)
				if rec.JA3 != tt.fingerprints.JA3 {
					t.Errorf("JA3 = %v, want %v", rec.JA3, tt.fingerprints.JA3)
				}
				if rec.JA3Hash != tt.fingerprints.JA3Hash {
					t.Errorf("JA3Hash = %v, want %v", rec.JA3Hash, tt.fingerprints.JA3Hash)
				}
			} else {
				if rec.JA4 != "" {
					t.Error("JA4 should be empty when fingerprints is nil")
				}
			}
		})
	}
}

func TestDefaultConfig(t *testing.T) {
	cfg := DefaultConfig()

	if cfg.Core.Interface != DefaultInterface {
		t.Errorf("Core.Interface = %v, want %v", cfg.Core.Interface, DefaultInterface)
	}
	if len(cfg.Core.ListenPorts) != 1 {
		t.Errorf("Core.ListenPorts length = %v, want 1", len(cfg.Core.ListenPorts))
	}
	if cfg.Core.ListenPorts[0] != DefaultPort {
		t.Errorf("Core.ListenPorts[0] = %v, want %v", cfg.Core.ListenPorts[0], DefaultPort)
	}
	if cfg.Core.BPFFilter != DefaultBPFFilter {
		t.Errorf("Core.BPFFilter = %v, want %v", cfg.Core.BPFFilter, DefaultBPFFilter)
	}
	if cfg.Core.FlowTimeoutSec != DefaultFlowTimeout {
		t.Errorf("Core.FlowTimeoutSec = %v, want %v", cfg.Core.FlowTimeoutSec, DefaultFlowTimeout)
	}
	if len(cfg.Outputs) != 0 {
		t.Errorf("Outputs length = %v, want 0", len(cfg.Outputs))
	}
}

func TestLogRecordConversion(t *testing.T) {
	// Test that NewLogRecord correctly converts TCPMeta options to comma-separated string
	clientHello := TLSClientHello{
		SrcIP:   "192.168.1.100",
		SrcPort: 54321,
		DstIP:   "10.0.0.1",
		DstPort: 443,
		TCPMeta: TCPMeta{
			WindowSize:  65535,
			MSS:         1460,
			WindowScale: 7,
			Options:     []string{"MSS", "WS", "SACK", "TS"},
		},
	}

	rec := NewLogRecord(clientHello, nil)

	// Verify options are joined with comma
	expectedOpts := "MSS,WS,SACK,TS"
	if rec.TCPOptions != expectedOpts {
		t.Errorf("TCPOptions = %v, want %v", rec.TCPOptions, expectedOpts)
	}
}

func TestLogRecordNoJA4Hash(t *testing.T) {
	// Verify that JA4Hash is NOT included in LogRecord per architecture decision
	clientHello := TLSClientHello{
		SrcIP:   "192.168.1.100",
		SrcPort: 54321,
		DstIP:   "10.0.0.1",
		DstPort: 443,
	}
	fingerprints := &Fingerprints{
		JA4:     "t13d1516h2_8daaf6152771_02cb136f2775",
		JA4Hash: "8daaf6152771_02cb136f2775", // Should NOT appear in LogRecord
		JA3:     "771,4865-4866-4867,0-23-65281,29-23-24,0",
		JA3Hash: "a0e6f06c7a6d15e5e3f0f0e6f06c7a6d",
	}

	rec := NewLogRecord(clientHello, fingerprints)

	// JA4Hash is NOT in LogRecord (architecture decision)
	// The JA4 format already includes its own hash portions

	// But JA4 should be present
	if rec.JA4 != fingerprints.JA4 {
		t.Errorf("JA4 = %v, want %v", rec.JA4, fingerprints.JA4)
	}

	// JA3Hash should still be present (it's the MD5 of JA3, which is needed)
	if rec.JA3Hash != fingerprints.JA3Hash {
		t.Errorf("JA3Hash = %v, want %v", rec.JA3Hash, fingerprints.JA3Hash)
	}
}

func TestOutputConfig(t *testing.T) {
	tests := []struct {
		name          string
		config        OutputConfig
		wantEnabled   bool
		wantAsyncBuf  int
	}{
		{
			name: "stdout output with async buffer",
			config: OutputConfig{
				Type:        "stdout",
				Enabled:     true,
				AsyncBuffer: 5000,
				Params:      map[string]string{},
			},
			wantEnabled:  true,
			wantAsyncBuf: 5000,
		},
		{
			name: "unix_socket output with default async buffer",
			config: OutputConfig{
				Type:        "unix_socket",
				Enabled:     true,
				AsyncBuffer: 0, // Default
				Params:      map[string]string{"socket_path": "/var/run/test.sock"},
			},
			wantEnabled:  true,
			wantAsyncBuf: 0,
		},
		{
			name: "disabled output",
			config: OutputConfig{
				Type:        "file",
				Enabled:     false,
				AsyncBuffer: 1000,
				Params:      map[string]string{"path": "/var/log/test.log"},
			},
			wantEnabled:  false,
			wantAsyncBuf: 1000,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if tt.config.Enabled != tt.wantEnabled {
				t.Errorf("Enabled = %v, want %v", tt.config.Enabled, tt.wantEnabled)
			}
			if tt.config.AsyncBuffer != tt.wantAsyncBuf {
				t.Errorf("AsyncBuffer = %v, want %v", tt.config.AsyncBuffer, tt.wantAsyncBuf)
			}
		})
	}
}
