package generate

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

	can "github.com/fiskerinc/cloud-services/pkg/can-go"
	"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/generated"
	"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/socketcan"
	examplecan "github.com/fiskerinc/cloud-services/pkg/can-go/testdata/gen/go/example"
	"golang.org/x/sync/errgroup"
	"gotest.tools/v3/assert"
)

func TestExampleDatabase_MarshalUnmarshal(t *testing.T) {
	for _, tt := range []struct {
		name string
		m    can.Message
		f    can.Frame
	}{
		{
			name: "IODebug",
			m: examplecan.NewIODebug().
				SetTestUnsigned(5).
				SetTestEnum(examplecan.IODebug_TestEnum_Two).
				SetTestSigned(-42).
				SetTestFloat(61.5).
				SetTestBoolEnum(examplecan.IODebug_TestBoolEnum_One).
				SetRawTestScaledEnum(examplecan.IODebug_TestScaledEnum_Four),
			f: can.Frame{
				ID:     500,
				Length: 6,
				Data:   can.Data{5, 2, 214, 123, 1, 2},
			},
		},

		{
			name: "MotorStatus1",
			m: examplecan.NewMotorStatus().
				SetSpeedKph(0.423).
				SetWheelError(true),
			f: can.Frame{
				ID:     400,
				Length: 3,
				Data:   can.Data{0x1, 0xa7, 0x1},
			},
		},

		{
			name: "MotorStatus2",
			m: examplecan.NewMotorStatus().
				SetSpeedKph(12),
			f: can.Frame{
				ID:     400,
				Length: 3,
				Data:   can.Data{0x00, 0xe0, 0x2e},
			},
		},
	} {
		tt := tt
		t.Run(tt.name, func(t *testing.T) {
			f, err := tt.m.MarshalFrame()
			assert.NilError(t, err)
			assert.Equal(t, tt.f, f)
			// allocate new message of same type as tt.m
			msg := reflect.New(reflect.ValueOf(tt.m).Elem().Type()).Interface().(generated.Message)
			assert.NilError(t, msg.UnmarshalFrame(f))
			assert.Assert(t, reflect.DeepEqual(tt.m, msg))
		})
	}
}

func TestExampleDatabase_UnmarshalFrame_Error(t *testing.T) {
	for _, tt := range []struct {
		name string
		f    can.Frame
		m    generated.Message
		err  string
	}{
		{
			name: "wrong ID",
			f:    can.Frame{ID: 11, Length: 8},
			m:    examplecan.NewSensorSonars(),
			err:  "unmarshal SensorSonars: expects ID 200 (got 00B#0000000000000000 with ID 11)",
		},
		{
			name: "wrong length",
			f:    can.Frame{ID: 200, Length: 4},
			m:    examplecan.NewSensorSonars(),
			err:  "unmarshal SensorSonars: expects length 8 (got 0C8#00000000 with length 4)",
		},
		{
			name: "remote frame",
			f:    can.Frame{ID: 200, Length: 8, IsRemote: true},
			m:    examplecan.NewSensorSonars(),
			err:  "unmarshal SensorSonars: expects non-remote frame (got remote frame 0C8#R8)",
		},
		{
			name: "extended ID",
			f:    can.Frame{ID: 200, Length: 8, IsExtended: true},
			m:    examplecan.NewSensorSonars(),
			err:  "unmarshal SensorSonars: expects standard ID (got 000000C8#0000000000000000 with extended ID)",
		},
	} {
		tt := tt
		t.Run(tt.name, func(t *testing.T) {
			assert.Equal(t, tt.err, tt.m.UnmarshalFrame(tt.f).Error())
		})
	}
}

func TestExampleDatabase_TestEnum_String(t *testing.T) {
	assert.Equal(t, "One", examplecan.IODebug_TestEnum_One.String())
	assert.Equal(t, "Two", examplecan.IODebug_TestEnum_Two.String())
	assert.Equal(t, "IODebug_TestEnum(3)", examplecan.IODebug_TestEnum(3).String())
}

func TestExampleDatabase_Message_String(t *testing.T) {
	const expected = "{WheelError: true, SpeedKph: 42km/h}"
	msg := examplecan.NewMotorStatus().
		SetSpeedKph(42).
		SetWheelError(true)
	assert.Equal(t, expected, msg.String())
	assert.Equal(t, expected, fmt.Sprintf("%v", msg))
}

func TestExampleDatabase_OutOfBoundsValue(t *testing.T) {
	const expected = examplecan.IODebug_TestEnum(63)
	actual := examplecan.NewIODebug().SetTestEnum(255).TestEnum()
	assert.Equal(t, expected, actual)
}

func TestExampleDatabase_MultiplexedSignals(t *testing.T) {
	// Given a message with multiplexed signals
	msg := examplecan.NewSensorSonars().
		SetErrCount(1).
		SetMux(1).
		SetLeft(20).
		SetMiddle(30).
		SetRight(40).
		SetRear(50).
		SetNoFiltLeft(60).
		SetNoFiltMiddle(70).
		SetNoFiltRight(80).
		SetNoFiltRear(90)
	for _, tt := range []struct {
		expectedMux          uint8
		expectedErrCount     uint16
		expectedLeft         float64
		expectedMiddle       float64
		expectedRight        float64
		expectedRear         float64
		expectedNoFiltLeft   float64
		expectedNoFiltMiddle float64
		expectedNoFiltRight  float64
		expectedNoFiltRear   float64
	}{
		{
			expectedMux:          0,
			expectedErrCount:     1,
			expectedLeft:         20,
			expectedMiddle:       30,
			expectedRight:        40,
			expectedRear:         50,
			expectedNoFiltLeft:   0,
			expectedNoFiltMiddle: 0,
			expectedNoFiltRight:  0,
			expectedNoFiltRear:   0,
		},
		{
			expectedMux:          1,
			expectedErrCount:     1,
			expectedLeft:         0,
			expectedMiddle:       0,
			expectedRight:        0,
			expectedRear:         0,
			expectedNoFiltLeft:   60,
			expectedNoFiltMiddle: 70,
			expectedNoFiltRight:  80,
			expectedNoFiltRear:   90,
		},
	} {
		tt := tt
		t.Run(fmt.Sprintf("mux=%v", tt.expectedMux), func(t *testing.T) {
			unmarshal1 := examplecan.NewSensorSonars()
			// When the multiplexer signal is 0 and we marshal the message
			// to a CAN frame
			msg.SetMux(tt.expectedMux)
			f1, err := msg.MarshalFrame()
			assert.NilError(t, err)
			// When we unmarshal the CAN frame back to a message
			assert.NilError(t, unmarshal1.UnmarshalFrame(f1))
			// Then only the multiplexed signals with multiplexer value 0
			// should be unmarshaled
			assert.Equal(t, tt.expectedMux, unmarshal1.Mux(), "Mux")
			assert.Equal(t, tt.expectedErrCount, unmarshal1.ErrCount(), "ErrCount")
			assert.Equal(t, tt.expectedLeft, unmarshal1.Left(), "Left")
			assert.Equal(t, tt.expectedMiddle, unmarshal1.Middle(), "Middle")
			assert.Equal(t, tt.expectedRight, unmarshal1.Right(), "Right")
			assert.Equal(t, tt.expectedRear, unmarshal1.Rear(), "Rear")
			assert.Equal(t, tt.expectedNoFiltLeft, unmarshal1.NoFiltLeft(), "NoFiltLeft")
			assert.Equal(t, tt.expectedNoFiltMiddle, unmarshal1.NoFiltMiddle(), "NoFiltMiddle")
			assert.Equal(t, tt.expectedNoFiltRight, unmarshal1.NoFiltRight(), "NoFiltRight")
			assert.Equal(t, tt.expectedNoFiltRear, unmarshal1.NoFiltRear(), "NoFiltRear")
		})
	}
}

func TestExampleDatabase_CopyFrom(t *testing.T) {
	// Given: an original message
	from := examplecan.NewIODebug().
		SetRawTestScaledEnum(examplecan.IODebug_TestScaledEnum_Four).
		SetTestBoolEnum(true).
		SetTestFloat(0.1).
		SetTestSigned(-10).
		SetTestUnsigned(10)
	// When: another message copies from the original message
	to := examplecan.NewIODebug().CopyFrom(from)
	// Then:
	// all fields should be equal...
	assert.Equal(t, from.String(), to.String())
	assert.Equal(t, from.TestScaledEnum(), to.TestScaledEnum())
	assert.Equal(t, from.TestBoolEnum(), to.TestBoolEnum())
	assert.Equal(t, from.TestFloat(), to.TestFloat())
	assert.Equal(t, from.TestSigned(), to.TestSigned())
	assert.Equal(t, from.TestUnsigned(), to.TestUnsigned())
	// ...and changes to the original should not affect the new message
	from.SetTestUnsigned(100)
	assert.Equal(t, uint8(10), to.TestUnsigned())
}

func TestExample_Nodes(t *testing.T) {
	const testTimeout = 2 * time.Second
	requireVCAN0(t)
	// given a DRIVER node and a MOTOR node
	motor := examplecan.NewMOTOR("can", "vcan0")
	driver := examplecan.NewDRIVER("can", "vcan0")
	// when starting them
	ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
	g, ctx := errgroup.WithContext(ctx)
	g.Go(func() error {
		return motor.Run(ctx)
	})
	g.Go(func() error {
		return driver.Run(ctx)
	})
	// and the MOTOR node is configured to send a speed report
	const expectedSpeedKph = 42
	motor.Lock()
	motor.Tx().MotorStatus().SetSpeedKph(expectedSpeedKph)
	motor.Tx().MotorStatus().SetCyclicTransmissionEnabled(true)
	motor.Unlock()
	// and the DRIVER node is configured to send a steering command
	const expectedSteer = -4
	driver.Lock()
	driver.Tx().MotorCommand().SetSteer(expectedSteer)
	driver.Tx().MotorCommand().SetCyclicTransmissionEnabled(true)
	driver.Unlock()
	// and the MOTOR node is listening for the steering command
	expectedSteerReceivedChan := make(chan struct{})
	motor.Lock()
	motor.Rx().MotorCommand().SetAfterReceiveHook(func(context.Context) error {
		motor.Lock()
		if motor.Rx().MotorCommand().Steer() == expectedSteer {
			close(expectedSteerReceivedChan)
			motor.Rx().MotorCommand().SetAfterReceiveHook(func(context.Context) error { return nil })
		}
		motor.Unlock()
		return nil
	})
	motor.Unlock()
	// and the DRIVER node is listening for the speed report
	expectedSpeedReceivedChan := make(chan struct{})
	driver.Lock()
	driver.Rx().MotorStatus().SetAfterReceiveHook(func(context.Context) error {
		driver.Lock()
		if driver.Rx().MotorStatus().SpeedKph() == expectedSpeedKph {
			close(expectedSpeedReceivedChan)
			driver.Rx().MotorStatus().SetAfterReceiveHook(func(context.Context) error { return nil })
		}
		driver.Unlock()
		return nil
	})
	driver.Unlock()
	// then the steer command transmitted by DRIVER should be received by MOTOR
	select {
	case <-expectedSteerReceivedChan:
	case <-ctx.Done():
		t.Fatalf("expected steer not received: %v", expectedSteer)
	}
	// and the speed report transmitted by MOTOR should be received by DRIVER
	select {
	case <-expectedSpeedReceivedChan:
	case <-ctx.Done():
		t.Fatalf("expected speed not received: %v", expectedSpeedKph)
	}
	cancel()
	assert.NilError(t, g.Wait())
}

func TestExample_Node_NoEmptyMessages(t *testing.T) {
	const testTimeout = 2 * time.Second
	requireVCAN0(t)
	// given a DRIVER node and a MOTOR node
	motor := examplecan.NewMOTOR("can", "vcan0")
	// when starting them
	ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
	handler := func(ctx context.Context) error {
		motor.Lock()
		motor.Tx().MotorStatus().SetSpeedKph(100).SetWheelError(true)
		motor.Unlock()
		return nil
	}
	motor.Tx().MotorStatus().SetBeforeTransmitHook(handler)
	motor.Tx().MotorStatus().SetCyclicTransmissionEnabled(true)
	c, err := socketcan.Dial("can", "vcan0")
	r := socketcan.NewReceiver(c)
	assert.NilError(t, err)
	g := errgroup.Group{}
	g.Go(func() error {
		return motor.Run(ctx)
	})
	assert.Assert(t, r.Receive())
	assert.Equal(t, examplecan.NewMotorStatus().SetSpeedKph(100).SetWheelError(true).Frame(), r.Frame())
	cancel()
	assert.NilError(t, g.Wait())
}

func requireVCAN0(t *testing.T) {
	t.Helper()
	if _, err := net.InterfaceByName("vcan0"); err != nil {
		t.Skip("interface vcan0 does not exist")
	}
}
