package gps

import (
	"testing"
)

func TestAppendChecksum(t *testing.T) {
	testCases := []struct {
		name     string
		input    []byte
		expected []byte
	}{
		{
			name:     "simple message",
			input:    []byte{0xB5, 0x62, 0x06, 0x24, 0x00, 0x00},
			expected: []byte{0xB5, 0x62, 0x06, 0x24, 0x00, 0x00, 0x2A, 0x84},
		},
		{
			name:     "CFG-NAV5 header only",
			input:    []byte{0xB5, 0x62, 0x06, 0x24, 0x24, 0x00},
			expected: []byte{0xB5, 0x62, 0x06, 0x24, 0x24, 0x00, 0x4E, 0xCC},
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			result := appendChecksum(tc.input)

			if len(result) != len(tc.expected) {
				t.Errorf("expected length %d, got %d", len(tc.expected), len(result))
				return
			}

			// Check checksum bytes (last two bytes)
			ckA := result[len(result)-2]
			ckB := result[len(result)-1]
			expectedCkA := tc.expected[len(tc.expected)-2]
			expectedCkB := tc.expected[len(tc.expected)-1]

			if ckA != expectedCkA || ckB != expectedCkB {
				t.Errorf("expected checksum 0x%02X 0x%02X, got 0x%02X 0x%02X",
					expectedCkA, expectedCkB, ckA, ckB)
			}
		})
	}
}

func TestAppendChecksumPreservesOriginal(t *testing.T) {
	input := []byte{0xB5, 0x62, 0x06, 0x24, 0x00, 0x00}
	original := make([]byte, len(input))
	copy(original, input)

	result := appendChecksum(input)

	// Verify original bytes are preserved
	for i := range input {
		if result[i] != original[i] {
			t.Errorf("byte %d changed: expected 0x%02X, got 0x%02X", i, original[i], result[i])
		}
	}

	// Verify two bytes were appended
	if len(result) != len(input)+2 {
		t.Errorf("expected length %d, got %d", len(input)+2, len(result))
	}
}

func TestNav5CmdConfig(t *testing.T) {
	// Verify nav5Cmd has expected values
	if nav5Cmd.DynModel != 6 {
		t.Errorf("expected DynModel 6 (airborne <1g), got %d", nav5Cmd.DynModel)
	}

	if nav5Cmd.FixMode != 3 {
		t.Errorf("expected FixMode 3 (auto 2D/3D), got %d", nav5Cmd.FixMode)
	}

	expectedMask := CfgNav5Dyn | CfgNav5MinEl | CfgNav5PosFixMode
	if nav5Cmd.Mask != expectedMask {
		t.Errorf("expected Mask 0x%04X, got 0x%04X", expectedMask, nav5Cmd.Mask)
	}

	if nav5Cmd.MinElev_deg != 5 {
		t.Errorf("expected MinElev_deg 5, got %d", nav5Cmd.MinElev_deg)
	}
}

func TestGNSSDisableCmdConfig(t *testing.T) {
	// Verify GNSSDisableCmd has expected structure
	if gnssDisableCmd.MsgVer != 0 {
		t.Errorf("expected MsgVer 0, got %d", gnssDisableCmd.MsgVer)
	}

	if gnssDisableCmd.NumTrkChHw != 0x20 {
		t.Errorf("expected NumTrkChHw 0x20, got 0x%02X", gnssDisableCmd.NumTrkChHw)
	}

	if len(gnssDisableCmd.ConfigBlocks) != 5 {
		t.Errorf("expected 5 config blocks, got %d", len(gnssDisableCmd.ConfigBlocks))
		return
	}

	// Verify GPS is enabled
	gpsBlock := gnssDisableCmd.ConfigBlocks[0]
	if gpsBlock.GnssId != 0 {
		t.Errorf("expected first block GnssId 0 (GPS), got %d", gpsBlock.GnssId)
	}
	if gpsBlock.Flags&CfgGnssEnable == 0 {
		t.Error("expected GPS to be enabled")
	}

	// Verify other GNSS are disabled
	for i := 1; i < len(gnssDisableCmd.ConfigBlocks); i++ {
		block := gnssDisableCmd.ConfigBlocks[i]
		if block.Flags&CfgGnssEnable != 0 {
			t.Errorf("expected block %d (GnssId %d) to be disabled", i, block.GnssId)
		}
	}
}

func TestNav5CmdWrite(t *testing.T) {
	buf := make([]byte, 64)
	nav5Cmd.Put42Bytes(buf)

	// Verify sync chars
	if buf[0] != 0xB5 || buf[1] != 0x62 {
		t.Errorf("expected sync 0xB5 0x62, got 0x%02X 0x%02X", buf[0], buf[1])
	}

	// Verify class/id
	if buf[2] != 0x06 || buf[3] != 0x24 {
		t.Errorf("expected class/id 0x06 0x24, got 0x%02X 0x%02X", buf[2], buf[3])
	}

	// Verify DynModel at offset 8
	if buf[8] != 6 {
		t.Errorf("expected DynModel 6, got %d", buf[8])
	}
}

func TestGNSSDisableCmdWrite(t *testing.T) {
	buf := make([]byte, 64)
	err := gnssDisableCmd.Put(buf)
	if err != nil {
		t.Errorf("unexpected error, likely buffer too short for data: %v", err)
	}

	// 6 header + 4 payload header + 5*8 blocks = 50 bytes
	const expectedLen = 6 + 4 + 5*8
	sz := gnssDisableCmd.Size()
	if sz != expectedLen {
		t.Errorf("expected %d bytes, got %d", expectedLen, sz)
	}

	// Verify sync chars
	if buf[0] != 0xB5 || buf[1] != 0x62 {
		t.Errorf("expected sync 0xB5 0x62, got 0x%02X 0x%02X", buf[0], buf[1])
	}

	// Verify class/id
	if buf[2] != 0x06 || buf[3] != 0x3E {
		t.Errorf("expected class/id 0x06 0x3E, got 0x%02X 0x%02X", buf[2], buf[3])
	}

	// Verify number of blocks
	if buf[9] != 5 {
		t.Errorf("expected 5 blocks, got %d", buf[9])
	}
}

func TestChecksumCalculation(t *testing.T) {
	// Test with known UBX message and expected checksum
	// This is a minimal CFG-NAV5 poll message
	msg := []byte{0xB5, 0x62, 0x06, 0x24, 0x00, 0x00}

	result := appendChecksum(msg)

	// Verify checksum by recalculating
	var ckA, ckB byte
	for i := 2; i < len(msg); i++ {
		ckA += msg[i]
		ckB += ckA
	}

	if result[6] != ckA || result[7] != ckB {
		t.Errorf("checksum mismatch: expected 0x%02X 0x%02X, got 0x%02X 0x%02X",
			ckA, ckB, result[6], result[7])
	}
}

func TestMessageRateCmdConfigs(t *testing.T) {
	testCases := []struct {
		name     string
		cmd      CfgMsg1
		msgClass byte
		msgID    byte
		rate     byte
	}{
		{"GGA", messageRateGGACmd, 0xF0, 0x00, 1},
		{"GLL", messageRateGLLCmd, 0xF0, 0x01, 1},
		{"GSA", messageRateGSACmd, 0xF0, 0x02, 0},
		{"GSV", messageRateGSVCmd, 0xF0, 0x03, 0},
		{"RMC", messageRateRMCCmd, 0xF0, 0x04, 1},
		{"VTG", messageRateVTGCmd, 0xF0, 0x05, 0},
		{"ZDA", messageRateZDACmd, 0xF0, 0x08, 0},
		{"TXT", messageRateTXTCmd, 0xF0, 0x41, 0},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			if tc.cmd.MsgClass != tc.msgClass {
				t.Errorf("expected MsgClass 0x%02X, got 0x%02X", tc.msgClass, tc.cmd.MsgClass)
			}
			if tc.cmd.MsgID != tc.msgID {
				t.Errorf("expected MsgID 0x%02X, got 0x%02X", tc.msgID, tc.cmd.MsgID)
			}
			if tc.cmd.Rate != tc.rate {
				t.Errorf("expected Rate %d, got %d", tc.rate, tc.cmd.Rate)
			}
		})
	}
}

func TestCfgMsg1Write(t *testing.T) {
	cmd := CfgMsg1{
		MsgClass: 0xF0,
		MsgID:    0x00,
		Rate:     1,
	}

	buf := make([]byte, 16)
	cmd.Put9Bytes(buf)
	// Verify sync chars
	if buf[0] != 0xB5 || buf[1] != 0x62 {
		t.Errorf("expected sync 0xB5 0x62, got 0x%02X 0x%02X", buf[0], buf[1])
	}

	// Verify class/id (0x06 0x01 for CFG-MSG)
	if buf[2] != 0x06 || buf[3] != 0x01 {
		t.Errorf("expected class/id 0x06 0x01, got 0x%02X 0x%02X", buf[2], buf[3])
	}

	// Verify length (3 bytes payload)
	if buf[4] != 3 || buf[5] != 0 {
		t.Errorf("expected length 3, got %d", uint16(buf[4])|uint16(buf[5])<<8)
	}

	// Verify payload
	if buf[6] != 0xF0 {
		t.Errorf("expected MsgClass 0xF0, got 0x%02X", buf[6])
	}
	if buf[7] != 0x00 {
		t.Errorf("expected MsgID 0x00, got 0x%02X", buf[7])
	}
	if buf[8] != 1 {
		t.Errorf("expected Rate 1, got %d", buf[8])
	}
}

func TestCfgMsg1ClassID(t *testing.T) {
	cmd := CfgMsg1{}
	if got := cmd.classID(); got != 0x0106 {
		t.Errorf("expected 0x0106, got 0x%04x", got)
	}
}

func TestMinimalMessageRatesConfig(t *testing.T) {
	// Verify the minimal config has correct rates set
	// GGA and RMC should be enabled (rate=1), others disabled (rate=0)
	expectedRates := map[byte]byte{
		0x00: 1, // GGA - enabled
		0x01: 1, // GLL - enabled
		0x02: 0, // GSA - disabled
		0x03: 0, // GSV - disabled
		0x04: 1, // RMC - enabled
		0x05: 0, // VTG - disabled
		0x08: 0, // ZDA - disabled
		0x41: 0, // TXT - disabled
	}

	commands := []CfgMsg1{
		messageRateGGACmd,
		messageRateGLLCmd,
		messageRateGSACmd,
		messageRateGSVCmd,
		messageRateRMCCmd,
		messageRateVTGCmd,
		messageRateZDACmd,
		messageRateTXTCmd,
	}

	for _, cmd := range commands {
		expectedRate, ok := expectedRates[cmd.MsgID]
		if !ok {
			t.Errorf("unexpected MsgID 0x%02X", cmd.MsgID)
			continue
		}
		if cmd.Rate != expectedRate {
			t.Errorf("MsgID 0x%02X: expected rate %d, got %d", cmd.MsgID, expectedRate, cmd.Rate)
		}
	}
}

func TestAllMessageRatesWriteCorrectBytes(t *testing.T) {
	// Test that each message rate command writes the correct bytes
	commands := []CfgMsg1{
		messageRateGGACmd,
		messageRateGLLCmd,
		messageRateGSACmd,
		messageRateGSVCmd,
		messageRateRMCCmd,
		messageRateVTGCmd,
		messageRateZDACmd,
		messageRateTXTCmd,
	}

	for _, cmd := range commands {
		buf := make([]byte, 16)
		cmd.Put9Bytes(buf)

		// Verify MsgClass in payload
		if buf[6] != 0xF0 {
			t.Errorf("MsgID 0x%02X: expected MsgClass 0xF0, got 0x%02X", cmd.MsgID, buf[6])
		}

		// Verify MsgID in payload
		if buf[7] != cmd.MsgID {
			t.Errorf("expected MsgID 0x%02X in payload, got 0x%02X", cmd.MsgID, buf[7])
		}

		// Verify Rate in payload
		if buf[8] != cmd.Rate {
			t.Errorf("MsgID 0x%02X: expected Rate %d, got %d", cmd.MsgID, cmd.Rate, buf[8])
		}
	}
}

func TestSetMessageRatesAllEnabledModifiesRate(t *testing.T) {
	// Verify that when we copy a command and set Rate=1, it works correctly
	cmd := messageRateGSACmd // This one is disabled by default
	if cmd.Rate != 0 {
		t.Errorf("expected GSA default rate 0, got %d", cmd.Rate)
	}

	// Simulate what SetMessageRatesAllEnabled does
	cmd.Rate = 1

	buf := make([]byte, 16)
	cmd.Put9Bytes(buf)
	if buf[8] != 1 {
		t.Errorf("expected Rate 1 in buffer, got %d", buf[8])
	}
}
