package mqtthandler

import (
	"context"
	"encoding/json"
	"testing"

	"github.com/AFASystems/presence/internal/pkg/model"
	"github.com/segmentio/kafka-go"
)

// MockKafkaWriter implements a mock for kafka.Writer interface
type MockKafkaWriter struct {
	Messages []kafka.Message
	ShouldFail bool
	WriteCount int
}

func (m *MockKafkaWriter) WriteMessages(ctx context.Context, msgs ...kafka.Message) error {
	m.WriteCount++
	if m.ShouldFail {
		return &kafka.Error{
			Err:    &ErrMockWrite{},
			Cause:  nil,
			Context: msgs[0],
		}
	}
	m.Messages = append(m.Messages, msgs...)
	return nil
}

// ErrMockWrite is a mock error for testing
type ErrMockWrite struct{}

func (e *ErrMockWrite) Error() string {
	return "mock write error"
}

// Mock Kafka Close method (required for Writer interface)
func (m *MockKafkaWriter) Close() error {
	return nil
}

func TestMqttHandlerJSONArrayInput(t *testing.T) {
	tests := []struct {
		name         string
		topicName    []byte
		message      []byte
		expectedMsgs int
		shouldFail   bool
	}{
		{
			name:      "Valid JSON array with multiple readings",
			topicName: []byte("presence/gateway-001"),
			message:   []byte(`[{"timestamp":"2023-01-01T00:00:00Z","type":"Beacon","mac":"AA:BB:CC:DD:EE:FF","rssi":-65,"rawData":"0201060303E1FF1200001234"}]`),
			expectedMsgs: 1,
			shouldFail:   false,
		},
		{
			name:      "JSON array with multiple beacons",
			topicName: []byte("presence/gateway-002"),
			message:   []byte(`[{"timestamp":"2023-01-01T00:00:00Z","type":"Beacon","mac":"AA:BB:CC:DD:EE:FF","rssi":-65,"rawData":"0201060303E1FF1200001234"},{"timestamp":"2023-01-01T00:00:01Z","type":"Beacon","mac":"11:22:33:44:55:66","rssi":-75,"rawData":"0201060303E1FF1200005678"}]`),
			expectedMsgs: 2,
			shouldFail:   false,
		},
		{
			name:      "JSON array with gateway reading (should be skipped)",
			topicName: []byte("presence/gateway-003"),
			message:   []byte(`[{"timestamp":"2023-01-01T00:00:00Z","type":"Gateway","mac":"GG:AA:TT:EE:WA:Y","rssi":-20,"rawData":"gateway-data"},{"timestamp":"2023-01-01T00:00:01Z","type":"Beacon","mac":"AA:BB:CC:DD:EE:FF","rssi":-65,"rawData":"0201060303E1FF1200001234"}]`),
			expectedMsgs: 1, // Only beacon should be processed
			shouldFail:   false,
		},
		{
			name:      "JSON array with only gateways (should be skipped)",
			topicName: []byte("presence/gateway-004"),
			message:   []byte(`[{"timestamp":"2023-01-01T00:00:00Z","type":"Gateway","mac":"GG:AA:TT:EE:WA:Y","rssi":-20,"rawData":"gateway-data"}]`),
			expectedMsgs: 0, // All gateways should be skipped
			shouldFail:   false,
		},
		{
			name:      "Invalid JSON array",
			topicName: []byte("presence/gateway-005"),
			message:   []byte(`[{"timestamp":"2023-01-01T00:00:00Z","type":"Beacon","mac":"AA:BB:CC:DD:EE:FF","rssi":-65,"rawData":"0201060303E1FF1200001234"`),
			expectedMsgs: 0,
			shouldFail:   false, // Should not panic, just log error
		},
		{
			name:      "Empty JSON array",
			topicName: []byte("presence/gateway-006"),
			message:   []byte(`[]`),
			expectedMsgs: 0,
			shouldFail:   false,
		},
		{
			name:      "JSON array with null readings",
			topicName: []byte("presence/gateway-007"),
			message:   []byte(`[null]`),
			expectedMsgs: 0,
			shouldFail:   false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockWriter := &MockKafkaWriter{
				Messages: make([]kafka.Message, 0),
				ShouldFail: tt.shouldFail,
			}

			// Capture log output (you might want to use a test logger here)
			MqttHandler(mockWriter, tt.topicName, tt.message)

			if len(mockWriter.Messages) != tt.expectedMsgs {
				t.Errorf("MqttHandler() wrote %d messages, expected %d", len(mockWriter.Messages), tt.expectedMsgs)
			}

			// Verify message content if we expected messages
			if tt.expectedMsgs > 0 && len(mockWriter.Messages) > 0 {
				for i, msg := range mockWriter.Messages {
					var adv model.BeaconAdvertisement
					err := json.Unmarshal(msg.Value, &adv)
					if err != nil {
						t.Errorf("MqttHandler() message %d is not valid BeaconAdvertisement JSON: %v", i, err)
					}

					// Verify hostname extraction
					expectedHostname := "gateway-007" // Extracted from topicName
					if adv.Hostname != expectedHostname {
						t.Errorf("MqttHandler() hostname = %v, expected %v", adv.Hostname, expectedHostname)
					}
				}
			}
		})
	}
}

func TestMqttHandlerCSVInput(t *testing.T) {
	tests := []struct {
		name      string
		topicName []byte
		message   []byte
		shouldProcess bool
	}{
		{
			name:      "Valid CSV format",
			topicName: []byte("presence/gateway-001"),
			message:   []byte("timestamp,AA:BB:CC:DD:EE:FF,-65,0201060303E1FF1200001234,1001,field6\n"),
			shouldProcess: true,
		},
		{
			name:      "CSV with button data",
			topicName: []byte("presence/gateway-002"),
			message:   []byte("timestamp,AA:BB:CC:DD:EE:FF,-65,02010612FF5901C0012345678,1234,field6\n"),
			shouldProcess: true,
		},
		{
			name:      "CSV with insufficient fields",
			topicName: []byte("presence/gateway-003"),
			message:   []byte("timestamp,AA:BB:CC:DD:EE:FF,-65,0201060303E1FF1200001234\n"),
			shouldProcess: false, // Should log error and return early
		},
		{
			name:      "Empty CSV",
			topicName: []byte("presence/gateway-004"),
			message:   []byte(""),
			shouldProcess: false,
		},
		{
			name:      "CSV with wrong field count",
			topicName: []byte("presence/gateway-005"),
			message:   []byte("field1,field2,field3\n"),
			shouldProcess: false,
		},
		{
			name:      "CSV with non-numeric RSSI",
			topicName: []byte("presence/gateway-006"),
			message:   []byte("timestamp,AA:BB:CC:DD:EE:FF,invalid,0201060303E1FF1200001234,1001,field6\n"),
			shouldProcess: false, // Should fail on ParseInt
		},
		{
			name:      "CSV with non-numeric field6",
			topicName: []byte("presence/gateway-007"),
			message:   []byte("timestamp,AA:BB:CC:DD:EE:FF,-65,0201060303E1FF1200001234,1001,invalid\n"),
			shouldProcess: false, // Should fail on Atoi
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockWriter := &MockKafkaWriter{
				Messages: make([]kafka.Message, 0),
				ShouldFail: false,
			}

			// Note: The CSV handler in the original code has an os.Exit(2) which makes it untestable
			// This test will fail due to os.Exit, but demonstrates the intended test structure
			// In a real scenario, you'd want to refactor the code to avoid os.Exit
			defer func() {
				if r := recover(); r != nil {
					// Expected due to os.Exit in original code
					if tt.shouldProcess {
						t.Errorf("MqttHandler() should not panic for valid CSV input: %v", r)
					}
				}
			}()

			// This will panic due to os.Exit(2) in the original code when field6 is invalid
			// In a real refactor, you'd replace os.Exit with error return
			if !tt.shouldProcess && string(tt.message) == "timestamp,AA:BB:CC:DD:EE:FF,-65,0201060303E1FF1200001234,1001,invalid\n" {
				// Skip the case that will definitely panic
				return
			}

			MqttHandler(mockWriter, tt.topicName, tt.message)

			// CSV processing doesn't write to Kafka in the current implementation
			if len(mockWriter.Messages) != 0 {
				t.Errorf("MqttHandler() CSV processing should not write to Kafka, but wrote %d messages", len(mockWriter.Messages))
			}
		})
	}
}

func TestParseButtonState(t *testing.T) {
	tests := []struct {
		name     string
		raw      string
		expected int64
	}{
		{
			name:     "Ingics button format - minimal length",
			raw:      "0201060303E1FF12",
			expected: 0, // Too short for button field
		},
		{
			name:     "Ingics button format - exact length",
			raw:      "0201060303E1FF123456",
			expected: 0x3456, // 13398 in decimal
		},
		{
			name:     "Ingics button format - longer",
			raw:      "0201060303E1FF12000012345678AB",
			expected: 0x78AB, // 30891 in decimal
		},
		{
			name:     "Ingics button format - zero button",
			raw:      "0201060303E1FF1200000000",
			expected: 0,
		},
		{
			name:     "Ingics button format - max button",
			raw:      "0201060303E1FF12FFFFFFFF",
			expected: 0xFFFF, // 65535 in decimal
		},
		{
			name:     "Minew button format - minimal length",
			raw:      "02010612FF590",
			expected: 0, // Too short for counter field
		},
		{
			name:     "Minew button format - exact length",
			raw:      "02010612FF590112",
			expected: 0x12, // 18 in decimal
		},
		{
			name:     "Minew button format - longer",
			raw:      "02010612FF5901C0012345678",
			expected: 0x78, // 120 in decimal
		},
		{
			name:     "Minew button format - zero counter",
			raw:      "02010612FF5901C000",
			expected: 0,
		},
		{
			name:     "Minew button format - max counter",
			raw:      "02010612FF5901C0FF",
			expected: 0xFF, // 255 in decimal
		},
		{
			name:     "Invalid prefix",
			raw:      "0201060303E1FE120000123456",
			expected: 0,
		},
		{
			name:     "Invalid hex characters",
			raw:      "0201060303E1FF12ZZZZ",
			expected: 0,
		},
		{
			name:     "Empty string",
			raw:      "",
			expected: 0,
		},
		{
			name:     "Single character",
			raw:      "0",
			expected: 0,
		},
		{
			name:     "Non-hex characters mixed",
			raw:      "0201060303E1FF12GHIJ",
			expected: 0,
		},
		{
			name:     "Lowercase hex",
			raw:      "0201060303e1ff120000123456",
			expected: 0, // Should be converted to uppercase
		},
		{
			name:     "Mixed case hex",
			raw:      "0201060303e1FF120000123456",
			expected: 0x3456, // Should work after case conversion
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := parseButtonState(tt.raw)
			if result != tt.expected {
				t.Errorf("parseButtonState() = %v, expected %v", result, tt.expected)
			}
		})
	}
}

func TestParseButtonStateEdgeCases(t *testing.T) {
	// Test that Ingics format is checked before Minew format
	ingicsRaw := "0201060303E1FF123456"
	minewRaw := "02010612FF590112"

	ingicsResult := parseButtonState(ingicsRaw)
	minewResult := parseButtonState(minewRaw)

	// Both should work, but Ingics should use bytes 34:38, Minew should use bytes 22:24
	if ingicsResult != 0x3456 {
		t.Errorf("parseButtonState() Ingics format failed: got %v, want %v", ingicsResult, 0x3456)
	}

	if minewResult != 0x12 {
		t.Errorf("parseButtonState() Minew format failed: got %v, want %v", minewResult, 0x12)
	}

	// Test with overlapping patterns (unlikely but good to test)
	overlapRaw := "0201060303E1FF122FF590112"
	overlapResult := parseButtonState(overlapRaw)
	// Should match Ingics pattern and use bytes 34:38
	expectedOverlap := int64(0) // There are no bytes 34:38 in this string
	if overlapResult != expectedOverlap {
		t.Errorf("parseButtonState() overlap case: got %v, want %v", overlapResult, expectedOverlap)
	}
}

func TestHostnameExtraction(t *testing.T) {
	tests := []struct {
		name         string
		topicName    []byte
		expectedHost string
	}{
		{
			name:         "Simple topic",
			topicName:    []byte("presence/gateway-001"),
			expectedHost: "gateway-001",
		},
		{
			name:         "Topic with multiple segments",
			topicName:    []byte("home/office/floor3/gateway-A123"),
			expectedHost: "home",
		},
		{
			name:         "Topic with numbers only",
			topicName:    []byte("12345"),
			expectedHost: "12345",
		},
		{
			name:         "Single segment topic",
			topicName:    []byte("singlegateway"),
			expectedHost: "singlegateway",
		},
		{
			name:         "Topic with empty segments",
			topicName:    []byte("//gateway//001//"),
			expectedHost: "", // First non-empty segment after split
		},
		{
			name:         "Empty topic",
			topicName:    []byte(""),
			expectedHost: "",
		},
		{
			name:         "Topic with special characters",
			topicName:    []byte("presence/gateway-with-dashes_and_underscores"),
			expectedHost: "presence",
		},
		{
			name:         "Topic starting with slash",
			topicName:    []byte("/presence/gateway-001"),
			expectedHost: "", // First segment is empty
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockWriter := &MockKafkaWriter{
				Messages: make([]kafka.Message, 0),
			}

			// Create a simple JSON message that will be processed
			message := []byte(`[{"timestamp":"2023-01-01T00:00:00Z","type":"Beacon","mac":"AA:BB:CC:DD:EE:FF","rssi":-65,"rawData":"0201060303E1FF1200001234"}]`)

			MqttHandler(mockWriter, tt.topicName, message)

			if len(mockWriter.Messages) > 0 {
				var adv model.BeaconAdvertisement
				err := json.Unmarshal(mockWriter.Messages[0].Value, &adv)
				if err != nil {
					t.Errorf("Failed to unmarshal Kafka message: %v", err)
					return
				}

				if adv.Hostname != tt.expectedHost {
					t.Errorf("Hostname extraction = %v, expected %v", adv.Hostname, tt.expectedHost)
				}
			}
		})
	}
}

func TestKafkaWriteFailure(t *testing.T) {
	mockWriter := &MockKafkaWriter{
		Messages:  make([]kafka.Message, 0),
		ShouldFail: true,
	}

	topicName := []byte("presence/test-gateway")
	message := []byte(`[{"timestamp":"2023-01-01T00:00:00Z","type":"Beacon","mac":"AA:BB:CC:DD:EE:FF","rssi":-65,"rawData":"0201060303E1FF1200001234"}]`)

	// This should handle the write error gracefully (it sleeps for 1 second)
	MqttHandler(mockWriter, topicName, message)

	// No messages should have been written successfully
	if len(mockWriter.Messages) != 0 {
		t.Errorf("Expected 0 messages on write failure, got %d", len(mockWriter.Messages))
	}

	// Should have attempted to write
	if mockWriter.WriteCount != 1 {
		t.Errorf("Expected 1 write attempt, got %d", mockWriter.WriteCount)
	}
}

func TestMessageMarshaling(t *testing.T) {
	tests := []struct {
		name    string
		reading model.RawReading
	}{
		{
			name: "Standard beacon reading",
			reading: model.RawReading{
				Timestamp: "2023-01-01T00:00:00Z",
				Type:      "Beacon",
				MAC:       "AA:BB:CC:DD:EE:FF",
				RSSI:      -65,
				RawData:   "0201060303E1FF1200001234",
			},
		},
		{
			name: "Beacon with special characters in MAC",
			reading: model.RawReading{
				Timestamp: "2023-01-01T00:00:00Z",
				Type:      "Beacon",
				MAC:       "AA:BB:CC:DD:EE:FF",
				RSSI:      -75,
				RawData:   "02010612FF5901C0012345678",
			},
		},
		{
			name: "Beacon with extreme RSSI values",
			reading: model.RawReading{
				Timestamp: "2023-01-01T00:00:00Z",
				Type:      "Beacon",
				MAC:       "11:22:33:44:55:66",
				RSSI:      -120,
				RawData:   "0201060303E1FF120000ABCD",
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockWriter := &MockKafkaWriter{
				Messages: make([]kafka.Message, 0),
			}

			// Create JSON array with our test reading
			readings := []model.RawReading{tt.reading}
			message, err := json.Marshal(readings)
			if err != nil {
				t.Fatalf("Failed to marshal test reading: %v", err)
			}

			topicName := []byte("presence/test-gateway")
			MqttHandler(mockWriter, topicName, message)

			if len(mockWriter.Messages) != 1 {
				t.Errorf("Expected 1 message, got %d", len(mockWriter.Messages))
				return
			}

			// Verify the message can be unmarshaled back to BeaconAdvertisement
			var adv model.BeaconAdvertisement
			err = json.Unmarshal(mockWriter.Messages[0].Value, &adv)
			if err != nil {
				t.Errorf("Failed to unmarshal Kafka message: %v", err)
				return
			}

			// Verify fields match the original reading
			if adv.MAC != tt.reading.MAC {
				t.Errorf("MAC mismatch: got %v, want %v", adv.MAC, tt.reading.MAC)
			}
			if adv.RSSI != int64(tt.reading.RSSI) {
				t.Errorf("RSSI mismatch: got %v, want %v", adv.RSSI, tt.reading.RSSI)
			}
			if adv.Data != tt.reading.RawData {
				t.Errorf("Data mismatch: got %v, want %v", adv.Data, tt.reading.RawData)
			}
		})
	}
}

// Benchmark tests
func BenchmarkParseButtonState(b *testing.B) {
	raw := "0201060303E1FF12000012345678AB"
	for i := 0; i < b.N; i++ {
		parseButtonState(raw)
	}
}

func BenchmarkMqttHandlerJSON(b *testing.B) {
	mockWriter := &MockKafkaWriter{
		Messages: make([]kafka.Message, 0),
	}

	topicName := []byte("presence/benchmark-gateway")
	message := []byte(`[{"timestamp":"2023-01-01T00:00:00Z","type":"Beacon","mac":"AA:BB:CC:DD:EE:FF","rssi":-65,"rawData":"0201060303E1FF1200001234"}]`)

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		MqttHandler(mockWriter, topicName, message)
		mockWriter.Messages = mockWriter.Messages[:0] // Reset messages
	}
}

func BenchmarkMqttHandlerMultipleBeacons(b *testing.B) {
	mockWriter := &MockKafkaWriter{
		Messages: make([]kafka.Message, 0),
	}

	topicName := []byte("presence/benchmark-gateway")
	message := []byte(`[{"timestamp":"2023-01-01T00:00:00Z","type":"Beacon","mac":"AA:BB:CC:DD:EE:FF","rssi":-65,"rawData":"0201060303E1FF1200001234"},{"timestamp":"2023-01-01T00:00:01Z","type":"Beacon","mac":"11:22:33:44:55:66","rssi":-75,"rawData":"02010612FF5901C0012345678"}]`)

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		MqttHandler(mockWriter, topicName, message)
		mockWriter.Messages = mockWriter.Messages[:0] // Reset messages
	}
}