package test

import (
	"testing"

	"github.com/AFASystems/presence/internal/pkg/common/utils"
	"github.com/AFASystems/presence/internal/pkg/model"
)

func TestCalculateDistance(t *testing.T) {
	tests := []struct {
		name     string
		adv      model.BeaconAdvertisement
		expected float64
	}{
		{
			name: "Strong signal - close distance",
			adv: model.BeaconAdvertisement{
				RSSI:    -30,
				TXPower: "59", // 89 in decimal
			},
			expected: 0.89976, // Close to minimum
		},
		{
			name: "Medium signal",
			adv: model.BeaconAdvertisement{
				RSSI:    -65,
				TXPower: "59",
			},
			expected: 1.5, // Medium distance
		},
		{
			name: "Weak signal - far distance",
			adv: model.BeaconAdvertisement{
				RSSI:    -95,
				TXPower: "59",
			},
			expected: 8.0, // Far distance
		},
		{
			name: "Equal RSSI and TX power",
			adv: model.BeaconAdvertisement{
				RSSI:    -59,
				TXPower: "59",
			},
			expected: 1.0, // Ratio = 1.0
		},
		{
			name: "Very strong signal",
			adv: model.BeaconAdvertisement{
				RSSI:    -10,
				TXPower: "59",
			},
			expected: 0.89976, // Minimum distance
		},
		{
			name: "Negative TX power (two's complement)",
			adv: model.BeaconAdvertisement{
				RSSI:    -70,
				TXPower: "C6", // -58 in decimal
			},
			expected: 1.2, // Medium distance
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := utils.CalculateDistance(tt.adv)
			// Allow for small floating point differences
			if result < tt.expected*0.9 || result > tt.expected*1.1 {
				t.Errorf("CalculateDistance() = %v, expected around %v", result, tt.expected)
			}
		})
	}
}

func TestCalculateDistanceEdgeCases(t *testing.T) {
	tests := []struct {
		name     string
		adv      model.BeaconAdvertisement
		expected float64
	}{
		{
			name: "Zero RSSI",
			adv: model.BeaconAdvertisement{
				RSSI:    0,
				TXPower: "59",
			},
			expected: 0.0,
		},
		{
			name: "Invalid TX power",
			adv: model.BeaconAdvertisement{
				RSSI:    -50,
				TXPower: "XYZ",
			},
			expected: 0.0, // twosComp returns 0 for invalid input
		},
	}

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

func TestValidateRSSI(t *testing.T) {
	tests := []struct {
		name     string
		rssi     int64
		expected bool
	}{
		{
			name:     "Valid RSSI - strong signal",
			rssi:     -30,
			expected: true,
		},
		{
			name:     "Valid RSSI - weak signal",
			rssi:     -100,
			expected: true,
		},
		{
			name:     "Valid RSSI - boundary low",
			rssi:     -120,
			expected: true,
		},
		{
			name:     "Valid RSSI - boundary high",
			rssi:     0,
			expected: true,
		},
		{
			name:     "Invalid RSSI - too strong",
			rssi:     10,
			expected: false,
		},
		{
			name:     "Invalid RSSI - too weak",
			rssi:     -130,
			expected: false,
		},
		{
			name:     "Invalid RSSI - just below boundary",
			rssi:     -121,
			expected: false,
		},
		{
			name:     "Invalid RSSI - just above boundary",
			rssi:     1,
			expected: false,
		},
	}

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

func TestValidateTXPower(t *testing.T) {
	tests := []struct {
		name     string
		txPower  string
		expected bool
	}{
		{
			name:     "Valid TX power - positive",
			txPower:  "59",
			expected: true,
		},
		{
			name:     "Valid TX power - negative",
			txPower:  "C6",
			expected: true,
		},
		{
			name:     "Valid TX power - zero",
			txPower:  "00",
			expected: true,
		},
		{
			name:     "Valid TX power - max positive",
			txPower:  "7F",
			expected: true,
		},
		{
			name:     "Valid TX power - max negative",
			txPower:  "80",
			expected: true,
		},
		{
			name:     "Valid TX power - boundary negative",
			txPower:  "81", // -127
			expected: true,
		},
		{
			name:     "Invalid TX power string",
			txPower:  "XYZ",
			expected: true, // twosComp returns 0, which is valid
		},
		{
			name:     "Empty TX power",
			txPower:  "",
			expected: true, // twosComp returns 0, which is valid
		},
	}

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

func TestCalculateDistanceConsistency(t *testing.T) {
	// Test that the function is deterministic
	adv := model.BeaconAdvertisement{
		RSSI:    -65,
		TXPower: "59",
	}

	result1 := utils.CalculateDistance(adv)
	result2 := utils.CalculateDistance(adv)

	if result1 != result2 {
		t.Errorf("CalculateDistance() is not deterministic: %v != %v", result1, result2)
	}
}

func TestCalculateDistanceRealWorldScenarios(t *testing.T) {
	scenarios := []struct {
		name          string
		rssi          int64
		txPower       string
		expectedRange [2]float64 // min, max expected range
	}{
		{
			name:          "Beacon very close (1m)",
			rssi:          -45,
			txPower:       "59", // 89 decimal
			expectedRange: [2]float64{0.5, 1.5},
		},
		{
			name:          "Beacon at medium distance (5m)",
			rssi:          -75,
			txPower:       "59",
			expectedRange: [2]float64{3.0, 8.0},
		},
		{
			name:          "Beacon far away (15m)",
			rssi:          -95,
			txPower:       "59",
			expectedRange: [2]float64{10.0, 25.0},
		},
	}

	for _, scenario := range scenarios {
		t.Run(scenario.name, func(t *testing.T) {
			adv := model.BeaconAdvertisement{
				RSSI:    scenario.rssi,
				TXPower: scenario.txPower,
			}
			result := utils.CalculateDistance(adv)

			if result < scenario.expectedRange[0] || result > scenario.expectedRange[1] {
				t.Errorf("CalculateDistance() = %v, expected range %v", result, scenario.expectedRange)
			}
		})
	}
}

// Benchmark tests
func BenchmarkCalculateDistance(b *testing.B) {
	adv := model.BeaconAdvertisement{
		RSSI:    -65,
		TXPower: "59",
	}

	for i := 0; i < b.N; i++ {
		utils.CalculateDistance(adv)
	}
}
