package unixsocket

import (
	"context"
	"fmt"
	"net"
	"os"
	"testing"
	"time"

	"github.com/antitbone/ja4/correlator/internal/domain"
)

func TestParseJSONEvent_Apache(t *testing.T) {
	data := []byte(`{
		"src_ip": "192.168.1.1",
		"src_port": 8080,
		"dst_ip": "10.0.0.1",
		"dst_port": 80,
		"timestamp": 1704110400000000000,
		"method": "GET",
		"path": "/api/test",
		"header_host": "example.com",
		"header_user_agent": "Mozilla/5.0"
	}`)

	event, err := parseJSONEvent(data, "A")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	if event.SrcIP != "192.168.1.1" {
		t.Errorf("expected src_ip 192.168.1.1, got %s", event.SrcIP)
	}
	if event.SrcPort != 8080 {
		t.Errorf("expected src_port 8080, got %d", event.SrcPort)
	}
	if event.Headers["host"] != "example.com" {
		t.Errorf("expected header host example.com, got %s", event.Headers["host"])
	}
	if event.Headers["user_agent"] != "Mozilla/5.0" {
		t.Errorf("expected header_user_agent Mozilla/5.0, got %s", event.Headers["user_agent"])
	}
	if event.Source != domain.SourceA {
		t.Errorf("expected source A, got %s", event.Source)
	}
	expectedTs := time.Unix(0, 1704110400000000000)
	if !event.Timestamp.Equal(expectedTs) {
		t.Errorf("expected timestamp %v, got %v", expectedTs, event.Timestamp)
	}
}

func TestParseJSONEvent_Network(t *testing.T) {
	data := []byte(`{
		"src_ip": "192.168.1.1",
		"src_port": 8080,
		"dst_ip": "10.0.0.1",
		"dst_port": 443,
		"timestamp": 1704110400000000000,
		"ja3": "abc123def456",
		"ja4": "xyz789",
		"tcp_meta_flags": "SYN"
	}`)

	event, err := parseJSONEvent(data, "B")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	if event.SrcIP != "192.168.1.1" {
		t.Errorf("expected src_ip 192.168.1.1, got %s", event.SrcIP)
	}
	if event.Extra["ja3"] != "abc123def456" {
		t.Errorf("expected ja3 abc123def456, got %v", event.Extra["ja3"])
	}
	if event.Source != domain.SourceB {
		t.Errorf("expected source B, got %s", event.Source)
	}
	// Network source now uses payload timestamp if available
	expectedTs := time.Unix(0, 1704110400000000000)
	if !event.Timestamp.Equal(expectedTs) {
		t.Errorf("expected network timestamp %v, got %v", expectedTs, event.Timestamp)
	}
}

func TestParseJSONEvent_InvalidJSON(t *testing.T) {
	data := []byte(`{invalid json}`)

	_, err := parseJSONEvent(data, "")
	if err == nil {
		t.Error("expected error for invalid JSON")
	}
}

func TestParseJSONEvent_MissingFields(t *testing.T) {
	data := []byte(`{"other_field": "value"}`)

	_, err := parseJSONEvent(data, "")
	if err == nil {
		t.Error("expected error for missing src_ip/src_port")
	}
}

func TestParseJSONEvent_SourceARequiresNumericTimestamp(t *testing.T) {
	data := []byte(`{
		"src_ip": "192.168.1.1",
		"src_port": 8080,
		"time": "2024-01-01T12:00:00Z"
	}`)

	_, err := parseJSONEvent(data, "A")
	if err == nil {
		t.Fatal("expected error for source A without numeric timestamp")
	}
}

func TestParseJSONEvent_SourceBUsesPayloadTimestamp(t *testing.T) {
	expectedTs := int64(1704110400000000000)
	data := []byte(`{
		"src_ip": "192.168.1.1",
		"src_port": 8080,
		"timestamp": 1704110400000000000
	}`)

	event, err := parseJSONEvent(data, "B")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	expectedTime := time.Unix(0, expectedTs)
	if !event.Timestamp.Equal(expectedTime) {
		t.Errorf("expected source B to use payload timestamp %v, got %v", expectedTime, event.Timestamp)
	}
}

func TestParseJSONEvent_SourceBUsesTimeField(t *testing.T) {
	data := []byte(`{
		"src_ip": "192.168.1.1",
		"src_port": 8080,
		"time": "2024-01-01T12:00:00Z"
	}`)

	event, err := parseJSONEvent(data, "B")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	expectedTime := time.Unix(0, 1704110400000000000)
	if !event.Timestamp.Equal(expectedTime) {
		t.Errorf("expected source B to use time field %v, got %v", expectedTime, event.Timestamp)
	}
}

func TestParseJSONEvent_SourceBFallbackToNow(t *testing.T) {
	data := []byte(`{
		"src_ip": "192.168.1.1",
		"src_port": 8080
	}`)

	before := time.Now()
	event, err := parseJSONEvent(data, "B")
	after := time.Now()
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	if event.Timestamp.Before(before.Add(-2*time.Second)) || event.Timestamp.After(after.Add(2*time.Second)) {
		t.Errorf("expected source B timestamp near now, got %v", event.Timestamp)
	}
}

func TestParseJSONEvent_ExplicitSourceType(t *testing.T) {
	tests := []struct {
		name       string
		data       string
		sourceType string
		expected   domain.EventSource
	}{
		{
			name:       "explicit A",
			data:       `{"src_ip": "192.168.1.1", "src_port": 8080, "timestamp": 1704110400000000000}`,
			sourceType: "A",
			expected:   domain.SourceA,
		},
		{
			name:       "explicit B",
			data:       `{"src_ip": "192.168.1.1", "src_port": 8080}`,
			sourceType: "B",
			expected:   domain.SourceB,
		},
		{
			name:       "explicit apache",
			data:       `{"src_ip": "192.168.1.1", "src_port": 8080, "timestamp": 1704110400000000000}`,
			sourceType: "apache",
			expected:   domain.SourceA,
		},
		{
			name:       "explicit network",
			data:       `{"src_ip": "192.168.1.1", "src_port": 8080}`,
			sourceType: "network",
			expected:   domain.SourceB,
		},
		{
			name:       "auto-detect A with headers",
			data:       `{"src_ip": "192.168.1.1", "src_port": 8080, "timestamp": 1704110400000000000, "header_host": "example.com"}`,
			sourceType: "",
			expected:   domain.SourceA,
		},
		{
			name:       "auto-detect B without headers",
			data:       `{"src_ip": "192.168.1.1", "src_port": 8080, "ja3": "abc"}`,
			sourceType: "",
			expected:   domain.SourceB,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			event, err := parseJSONEvent([]byte(tt.data), tt.sourceType)
			if err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			if event.Source != tt.expected {
				t.Errorf("expected source %s, got %s", tt.expected, event.Source)
			}
		})
	}
}

func TestUnixSocketSource_Name(t *testing.T) {
	source := NewUnixSocketSource(Config{
		Name: "test_source",
		Path: "/tmp/test.sock",
	})

	if source.Name() != "test_source" {
		t.Errorf("expected name 'test_source', got %s", source.Name())
	}
}

func TestUnixSocketSource_StopWithoutStart(t *testing.T) {
	source := NewUnixSocketSource(Config{
		Name: "test_source",
		Path: "/tmp/test.sock",
	})

	// Should not panic
	err := source.Stop()
	if err != nil {
		t.Errorf("expected no error on stop without start, got %v", err)
	}
}

func TestUnixSocketSource_EmptyPath(t *testing.T) {
	source := NewUnixSocketSource(Config{
		Name: "test_source",
		Path: "",
	})

	ctx := context.Background()
	eventChan := make(chan *domain.NormalizedEvent, 10)

	err := source.Start(ctx, eventChan)
	if err == nil {
		t.Error("expected error for empty path")
	}
}

func TestGetString(t *testing.T) {
	m := map[string]any{
		"string": "hello",
		"int":    42,
		"nil":    nil,
	}

	v, ok := getString(m, "string")
	if !ok || v != "hello" {
		t.Errorf("expected 'hello', got %v, %v", v, ok)
	}

	_, ok = getString(m, "int")
	if ok {
		t.Error("expected false for int")
	}

	_, ok = getString(m, "missing")
	if ok {
		t.Error("expected false for missing key")
	}
}

func TestGetInt(t *testing.T) {
	m := map[string]any{
		"float":  42.5,
		"int":    42,
		"int64":  int64(42),
		"string": "42",
		"bad":    "not a number",
		"nil":    nil,
	}

	tests := []struct {
		key      string
		expected int
		ok       bool
	}{
		{"float", 0, false},
		{"int", 42, true},
		{"int64", 42, true},
		{"string", 42, true},
		{"bad", 0, false},
		{"nil", 0, false},
		{"missing", 0, false},
	}

	for _, tt := range tests {
		t.Run(tt.key, func(t *testing.T) {
			v, ok := getInt(m, tt.key)
			if ok != tt.ok {
				t.Errorf("getInt(%q) ok = %v, want %v", tt.key, ok, tt.ok)
			}
			if v != tt.expected {
				t.Errorf("getInt(%q) = %v, want %v", tt.key, v, tt.expected)
			}
		})
	}
}

func TestGetInt64(t *testing.T) {
	m := map[string]any{
		"float":  42.5,
		"int":    42,
		"int64":  int64(42),
		"string": "42",
		"bad":    "not a number",
		"nil":    nil,
	}

	tests := []struct {
		key      string
		expected int64
		ok       bool
	}{
		{"float", 0, false},
		{"int", 42, true},
		{"int64", 42, true},
		{"string", 42, true},
		{"bad", 0, false},
		{"nil", 0, false},
		{"missing", 0, false},
	}

	for _, tt := range tests {
		t.Run(tt.key, func(t *testing.T) {
			v, ok := getInt64(m, tt.key)
			if ok != tt.ok {
				t.Errorf("getInt64(%q) ok = %v, want %v", tt.key, ok, tt.ok)
			}
			if v != tt.expected {
				t.Errorf("getInt64(%q) = %v, want %v", tt.key, v, tt.expected)
			}
		})
	}
}

func TestParseJSONEvent_PortValidation(t *testing.T) {
	tests := []struct {
		name       string
		data       string
		sourceType string
		wantErr    bool
	}{
		{
			name:       "valid src_port",
			data:       `{"src_ip": "192.168.1.1", "src_port": 8080}`,
			sourceType: "B",
			wantErr:    false,
		},
		{
			name:       "src_port zero",
			data:       `{"src_ip": "192.168.1.1", "src_port": 0}`,
			sourceType: "B",
			wantErr:    true,
		},
		{
			name:       "src_port negative",
			data:       `{"src_ip": "192.168.1.1", "src_port": -1}`,
			sourceType: "B",
			wantErr:    true,
		},
		{
			name:       "src_port too high",
			data:       `{"src_ip": "192.168.1.1", "src_port": 70000}`,
			sourceType: "B",
			wantErr:    true,
		},
		{
			name:       "valid dst_port zero",
			data:       `{"src_ip": "192.168.1.1", "src_port": 8080, "dst_port": 0}`,
			sourceType: "B",
			wantErr:    false,
		},
		{
			name:       "dst_port too high",
			data:       `{"src_ip": "192.168.1.1", "src_port": 8080, "dst_port": 70000}`,
			sourceType: "B",
			wantErr:    true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			_, err := parseJSONEvent([]byte(tt.data), tt.sourceType)
			if (err != nil) != tt.wantErr {
				t.Errorf("parseJSONEvent() error = %v, wantErr %v", err, tt.wantErr)
			}
		})
	}
}

func TestParseJSONEvent_TimestampFallback(t *testing.T) {
	data := []byte(`{"src_ip": "192.168.1.1", "src_port": 8080}`)
	event, err := parseJSONEvent(data, "B")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	// For source B, timestamp is reception time
	if event.Timestamp.IsZero() {
		t.Error("expected non-zero timestamp")
	}
}

func TestUnixSocketSource_StartStopDatagram(t *testing.T) {
	tmpPath := "/tmp/test_logcorrelator_datagram.sock"
	// Clean up any existing socket
	os.Remove(tmpPath)

	source := NewUnixSocketSource(Config{
		Name:              "test_datagram",
		Path:              tmpPath,
		SourceType:        "B",
		SocketPermissions: 0666,
	})

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

	eventChan := make(chan *domain.NormalizedEvent, 10)

	err := source.Start(ctx, eventChan)
	if err != nil {
		t.Fatalf("failed to start source: %v", err)
	}

	// Give socket time to start
	time.Sleep(100 * time.Millisecond)

	// Verify socket file exists
	if _, err := os.Stat(tmpPath); os.IsNotExist(err) {
		t.Error("socket file should exist")
	}

	// Stop the source
	err = source.Stop()
	if err != nil {
		t.Errorf("failed to stop source: %v", err)
	}

	// Socket file should be cleaned up
	time.Sleep(100 * time.Millisecond)
	if _, err := os.Stat(tmpPath); !os.IsNotExist(err) {
		t.Error("socket file should be removed after stop")
	}
}

func TestUnixSocketSource_SendDatagram(t *testing.T) {
	tmpPath := "/tmp/test_logcorrelator_send.sock"
	os.Remove(tmpPath)

	source := NewUnixSocketSource(Config{
		Name:              "test_send",
		Path:              tmpPath,
		SourceType:        "B",
		SocketPermissions: 0666,
	})

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

	eventChan := make(chan *domain.NormalizedEvent, 10)

	err := source.Start(ctx, eventChan)
	if err != nil {
		t.Fatalf("failed to start source: %v", err)
	}

	// Give socket time to start
	time.Sleep(100 * time.Millisecond)

	// Connect and send a datagram
	conn, err := net.Dial("unixgram", tmpPath)
	if err != nil {
		t.Fatalf("failed to dial socket: %v", err)
	}
	defer conn.Close()

	data := []byte(`{"src_ip": "192.168.1.1", "src_port": 8080, "ja3": "test"}`)
	_, err = conn.Write(data)
	if err != nil {
		t.Fatalf("failed to write: %v", err)
	}

	// Wait for event
	select {
	case event := <-eventChan:
		if event.SrcIP != "192.168.1.1" {
			t.Errorf("expected src_ip 192.168.1.1, got %s", event.SrcIP)
		}
		if event.SrcPort != 8080 {
			t.Errorf("expected src_port 8080, got %d", event.SrcPort)
		}
	case <-time.After(2 * time.Second):
		t.Error("timeout waiting for event")
	case <-ctx.Done():
		t.Error("context cancelled")
	}

	err = source.Stop()
	if err != nil {
		t.Errorf("failed to stop source: %v", err)
	}
}

func TestUnixSocketSource_MultipleDatagrams(t *testing.T) {
	tmpPath := "/tmp/test_logcorrelator_multi.sock"
	os.Remove(tmpPath)

	source := NewUnixSocketSource(Config{
		Name:              "test_multi",
		Path:              tmpPath,
		SourceType:        "B",
		SocketPermissions: 0666,
	})

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

	eventChan := make(chan *domain.NormalizedEvent, 100)

	err := source.Start(ctx, eventChan)
	if err != nil {
		t.Fatalf("failed to start source: %v", err)
	}

	// Give socket time to start
	time.Sleep(100 * time.Millisecond)

	// Connect and send multiple datagrams
	conn, err := net.Dial("unixgram", tmpPath)
	if err != nil {
		t.Fatalf("failed to dial socket: %v", err)
	}
	defer conn.Close()

	for i := 0; i < 5; i++ {
		data := []byte(fmt.Sprintf(`{"src_ip": "192.168.1.%d", "src_port": %d, "ja3": "test%d"}`, i+1, 8080+i, i))
		_, err = conn.Write(data)
		if err != nil {
			t.Fatalf("failed to write datagram %d: %v", i, err)
		}
	}

	// Wait for all events
	received := 0
	timeout := time.After(3 * time.Second)
	for received < 5 {
		select {
		case event := <-eventChan:
			received++
			t.Logf("received event %d: src_ip=%s", received, event.SrcIP)
		case <-timeout:
			t.Errorf("timeout waiting for events, received %d/5", received)
			goto done
		case <-ctx.Done():
			t.Error("context cancelled")
			goto done
		}
	}

done:
	err = source.Stop()
	if err != nil {
		t.Errorf("failed to stop source: %v", err)
	}
}
