package analyzer

import (
	"encoding/json"
	"testing"

	troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
	"github.com/replicatedhq/troubleshoot/pkg/collect"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestAnalyzeBlockDevices(t *testing.T) {
	tests := []struct {
		name         string
		devices      []collect.BlockDeviceInfo
		hostAnalyzer *troubleshootv1beta2.BlockDevicesAnalyze
		result       []*AnalyzeResult
		expectErr    bool
	}{
		{
			name: "sdb == 1, pass when there is an empty /dev/sdb",
			devices: []collect.BlockDeviceInfo{
				{
					Name:       "sdb",
					KernelName: "sdb",
					Type:       "disk",
					Major:      8,
					Serial:     "disk1",
				},
			},
			hostAnalyzer: &troubleshootv1beta2.BlockDevicesAnalyze{
				Outcomes: []*troubleshootv1beta2.Outcome{
					{
						Pass: &troubleshootv1beta2.SingleOutcome{
							When:    "sdb == 1",
							Message: "Block device available",
						},
					},
				},
			},
			result: []*AnalyzeResult{
				{
					Title:   "Block Devices",
					IsPass:  true,
					Message: "Block device available",
				},
			},
		},
		{
			name: "sdb == 1, fail when partitioned",
			devices: []collect.BlockDeviceInfo{
				{
					Name:       "sdb",
					KernelName: "sdb",
					Type:       "disk",
					Major:      8,
					Serial:     "disk1",
				},
				{
					Name:             "sdb1",
					KernelName:       "sdb1",
					ParentKernelName: "sdb",
					Type:             "part",
					Major:            8,
					Minor:            1,
				},
			},
			hostAnalyzer: &troubleshootv1beta2.BlockDevicesAnalyze{
				Outcomes: []*troubleshootv1beta2.Outcome{
					{
						Pass: &troubleshootv1beta2.SingleOutcome{
							When:    "sdb == 1",
							Message: "Block device available",
						},
					},
					{
						Fail: &troubleshootv1beta2.SingleOutcome{
							Message: "No block device available",
						},
					},
				},
			},
			result: []*AnalyzeResult{
				{
					Title:   "Block Devices",
					IsFail:  true,
					Message: "No block device available",
				},
			},
		},
		{
			name: "sdb == 1, fail when it has a filesystem",
			devices: []collect.BlockDeviceInfo{
				{
					Name:           "sdb",
					KernelName:     "sdb",
					Type:           "disk",
					Major:          8,
					Serial:         "disk1",
					FilesystemType: "ext4",
				},
			},
			hostAnalyzer: &troubleshootv1beta2.BlockDevicesAnalyze{
				Outcomes: []*troubleshootv1beta2.Outcome{
					{
						Pass: &troubleshootv1beta2.SingleOutcome{
							When:    "sdb == 1",
							Message: "Block device available",
						},
					},
					{
						Fail: &troubleshootv1beta2.SingleOutcome{
							Message: "No block device available",
						},
					},
				},
			},
			result: []*AnalyzeResult{
				{
					Title:   "Block Devices",
					IsFail:  true,
					Message: "No block device available",
				},
			},
		},
		{
			name: ".* > 0, fail when only loop devices are found",
			devices: []collect.BlockDeviceInfo{
				{
					Name:       "loop0",
					KernelName: "loop0",
					Type:       "loop",
					Major:      7,
				},
			},
			hostAnalyzer: &troubleshootv1beta2.BlockDevicesAnalyze{
				Outcomes: []*troubleshootv1beta2.Outcome{
					{
						Pass: &troubleshootv1beta2.SingleOutcome{
							When:    ".* > 0",
							Message: "Block device available",
						},
					},
					{
						Fail: &troubleshootv1beta2.SingleOutcome{
							Message: "No block device available",
						},
					},
				},
			},
			result: []*AnalyzeResult{
				{
					Title:   "Block Devices",
					IsFail:  true,
					Message: "No block device available",
				},
			},
		},
		{
			name: ".* > 1, pass with unmounted partition",
			devices: []collect.BlockDeviceInfo{
				{
					Name:       "sdb",
					KernelName: "sdb",
					Type:       "disk",
					Major:      8,
					Serial:     "disk1",
				},
				{
					Name:             "sdb1",
					KernelName:       "sdb1",
					ParentKernelName: "sdb",
					Type:             "part",
					Major:            8,
					Minor:            1,
				},
			},
			hostAnalyzer: &troubleshootv1beta2.BlockDevicesAnalyze{
				IncludeUnmountedPartitions: true,
				Outcomes: []*troubleshootv1beta2.Outcome{
					{
						Pass: &troubleshootv1beta2.SingleOutcome{
							When:    ".* >= 1",
							Message: "Block device or partition available",
						},
					},
				},
			},
			result: []*AnalyzeResult{
				{
					Title:   "Block Devices",
					IsPass:  true,
					Message: "Block device or partition available",
				},
			},
		},
		{
			name: ".* = 2, pass with two unmounted partitions/devices of at least 10gb in size",
			devices: []collect.BlockDeviceInfo{
				{
					Name:       "sdb",
					KernelName: "sdb",
					Type:       "disk",
					Major:      8,
					Serial:     "disk1",
					Size:       1024 * 1024 * 1024 * 128,
				},
				{
					Name:             "sdb1",
					KernelName:       "sdb1",
					ParentKernelName: "sdb",
					Type:             "part",
					Major:            8,
					Minor:            1,
					Size:             1024 * 1024 * 1024 * 128,
				},
				{
					Name:       "sdc",
					KernelName: "sdc",
					Type:       "disk",
					Major:      8,
					Serial:     "disk2",
					Size:       1024 * 1024 * 1024 * 16,
				},
				{
					Name:       "sdd",
					KernelName: "sdd",
					Type:       "disk",
					Major:      8,
					Serial:     "disk3",
					Size:       1024 * 1024 * 1024 * 8,
				},
			},
			hostAnalyzer: &troubleshootv1beta2.BlockDevicesAnalyze{
				IncludeUnmountedPartitions: true,
				MinimumAcceptableSize:      1024 * 1024 * 1024 * 10,
				Outcomes: []*troubleshootv1beta2.Outcome{
					{
						Pass: &troubleshootv1beta2.SingleOutcome{
							When:    ".* = 2",
							Message: "Two block devices or partitions of >10gb size available",
						},
					},
				},
			},
			result: []*AnalyzeResult{
				{
					Title:   "Block Devices",
					IsPass:  true,
					Message: "Two block devices or partitions of >10gb size available",
				},
			},
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			req := require.New(t)
			result, err := analyzeHostBlockDevicesOutput(t, test.devices, test.hostAnalyzer)
			if test.expectErr {
				req.Error(err)
			} else {
				req.NoError(err)
			}
			assert.Equal(t, test.result, result)
		})
	}
}

// analyzeHostBlockDevicesOutput runs the host block device analyzer on marshaled fixture data (shared by match tests).
func analyzeHostBlockDevicesOutput(t *testing.T, devices []collect.BlockDeviceInfo, hostAnalyzer *troubleshootv1beta2.BlockDevicesAnalyze) ([]*AnalyzeResult, error) {
	t.Helper()
	b, err := json.Marshal(devices)
	require.NoError(t, err)
	getCollectedFileContents := func(string) ([]byte, error) { return b, nil }
	return (&AnalyzeHostBlockDevices{hostAnalyzer}).Analyze(getCollectedFileContents, nil)
}
