package hardware_test

import (
	"errors"
	"io"
	"testing"

	"github.com/golang/mock/gomock"
	"github.com/onsi/gomega"

	"github.com/aws/eks-anywhere/pkg/providers/tinkerbell/hardware"
	"github.com/aws/eks-anywhere/pkg/providers/tinkerbell/hardware/mocks"
)

func TestTranslateReadsAndWrites(t *testing.T) {
	ctrl := gomock.NewController(t)
	g := gomega.NewWithT(t)

	reader := mocks.NewMockMachineReader(ctrl)
	writer := mocks.NewMockMachineWriter(ctrl)
	validator := mocks.NewMockMachineValidator(ctrl)

	machine := hardware.Machine{
		Hostname: "foot-bar",
	}

	var receivedMachine hardware.Machine
	reader.EXPECT().Read().Return(machine, (error)(nil))
	validator.EXPECT().Validate(machine).Return((error)(nil))
	writer.EXPECT().
		Write(machine).
		Do(func(machine hardware.Machine) {
			receivedMachine = machine
		}).
		Return((error)(nil))

	err := hardware.Translate(reader, writer, validator)

	g.Expect(err).ToNot(gomega.HaveOccurred())
	g.Expect(receivedMachine).To(gomega.BeEquivalentTo(machine))
}

func TestTranslateWithReadError(t *testing.T) {
	ctrl := gomock.NewController(t)
	g := gomega.NewWithT(t)

	reader := mocks.NewMockMachineReader(ctrl)
	writer := mocks.NewMockMachineWriter(ctrl)
	validator := mocks.NewMockMachineValidator(ctrl)

	expect := errors.New("luck-number-10")

	reader.EXPECT().Read().Return(hardware.Machine{}, expect)

	err := hardware.Translate(reader, writer, validator)

	g.Expect(err.Error()).To(gomega.ContainSubstring(expect.Error()))
}

func TestTranslateWithWriteError(t *testing.T) {
	ctrl := gomock.NewController(t)
	g := gomega.NewWithT(t)

	reader := mocks.NewMockMachineReader(ctrl)
	writer := mocks.NewMockMachineWriter(ctrl)
	validator := mocks.NewMockMachineValidator(ctrl)

	machine := hardware.Machine{Hostname: "lucky-number-10"}
	expect := errors.New("luck-number-10")

	reader.EXPECT().Read().Return(machine, (error)(nil))
	validator.EXPECT().Validate(machine).Return((error)(nil))
	writer.EXPECT().Write(machine).Return(expect)

	err := hardware.Translate(reader, writer, validator)

	g.Expect(err.Error()).To(gomega.ContainSubstring(expect.Error()))
}

func TestTranslateReturnsEOFWhenReaderEOFs(t *testing.T) {
	ctrl := gomock.NewController(t)
	g := gomega.NewWithT(t)

	reader := mocks.NewMockMachineReader(ctrl)
	writer := mocks.NewMockMachineWriter(ctrl)
	validator := mocks.NewMockMachineValidator(ctrl)

	reader.EXPECT().Read().Return(hardware.Machine{}, io.EOF)

	err := hardware.Translate(reader, writer, validator)

	g.Expect(err).To(gomega.BeEquivalentTo(io.EOF))
}

func TestTranslateWithValidationError(t *testing.T) {
	ctrl := gomock.NewController(t)
	g := gomega.NewWithT(t)

	reader := mocks.NewMockMachineReader(ctrl)
	writer := mocks.NewMockMachineWriter(ctrl)
	validator := mocks.NewMockMachineValidator(ctrl)

	expect := errors.New("validation error")
	reader.EXPECT().Read().Return(hardware.Machine{}, (error)(nil))
	validator.EXPECT().Validate(hardware.Machine{}).Return(expect)

	err := hardware.Translate(reader, writer, validator)

	g.Expect(err.Error()).To(gomega.ContainSubstring(expect.Error()))
}

func TestTranslateAllReadsAndWritesMaskingEOF(t *testing.T) {
	ctrl := gomock.NewController(t)
	g := gomega.NewWithT(t)

	reader := mocks.NewMockMachineReader(ctrl)
	writer := mocks.NewMockMachineWriter(ctrl)
	validator := mocks.NewMockMachineValidator(ctrl)

	machine := hardware.Machine{Hostname: "lucky-number-10"}

	// use readCount to track how many times the Read() call has been made. On
	// the second call we return io.EOF.
	var readCount int
	reader.EXPECT().
		Read().
		Times(2).
		DoAndReturn(func() (hardware.Machine, error) {
			if readCount == 1 {
				return hardware.Machine{}, io.EOF
			}

			readCount++
			return machine, nil
		})

	validator.EXPECT().Validate(machine).Return((error)(nil))

	// we only expect Write() to bec alled once because the io.EOF shouldn't result in
	// a write.
	writer.EXPECT().Write(machine).Times(1).Return((error)(nil))

	err := hardware.TranslateAll(reader, writer, validator)

	g.Expect(err).ToNot(gomega.HaveOccurred())
}

func TestTranslateAllWithReadError(t *testing.T) {
	ctrl := gomock.NewController(t)
	g := gomega.NewWithT(t)

	reader := mocks.NewMockMachineReader(ctrl)
	writer := mocks.NewMockMachineWriter(ctrl)
	validator := mocks.NewMockMachineValidator(ctrl)

	expect := errors.New("luck-number-10")

	reader.EXPECT().Read().Return(hardware.Machine{}, expect)

	err := hardware.TranslateAll(reader, writer, validator)

	g.Expect(err.Error()).To(gomega.ContainSubstring(expect.Error()))
}

func TestTranslateAllWithWriteError(t *testing.T) {
	ctrl := gomock.NewController(t)
	g := gomega.NewWithT(t)

	reader := mocks.NewMockMachineReader(ctrl)
	writer := mocks.NewMockMachineWriter(ctrl)
	validator := mocks.NewMockMachineValidator(ctrl)

	machine := hardware.Machine{Hostname: "lucky-number-10"}
	expect := errors.New("luck-number-10")

	reader.EXPECT().Read().Return(machine, (error)(nil))
	validator.EXPECT().Validate(machine).Return((error)(nil))
	writer.EXPECT().Write(machine).Return(expect)

	err := hardware.TranslateAll(reader, writer, validator)

	g.Expect(err.Error()).To(gomega.ContainSubstring(expect.Error()))
}

func TestTranslateAllWithValidationError(t *testing.T) {
	ctrl := gomock.NewController(t)
	g := gomega.NewWithT(t)

	reader := mocks.NewMockMachineReader(ctrl)
	writer := mocks.NewMockMachineWriter(ctrl)
	validator := mocks.NewMockMachineValidator(ctrl)

	expect := errors.New("validation error")
	reader.EXPECT().Read().Return(hardware.Machine{}, (error)(nil))
	validator.EXPECT().Validate(hardware.Machine{}).Return(expect)

	err := hardware.TranslateAll(reader, writer, validator)

	g.Expect(err.Error()).To(gomega.ContainSubstring(expect.Error()))
}