package analyzer

import (
	"encoding/json"
	"testing"

	"github.com/pkg/errors"
	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 collectorToBytes(t *testing.T, collector any) []byte {
	jsonData, err := json.Marshal(collector)
	require.NoError(t, err)
	return jsonData
}

func TestAnalyzeHostCollectorResults(t *testing.T) {
	tests := []struct {
		name             string
		outcomes         []*troubleshootv1beta2.Outcome
		collectedContent []collectedContent
		expectResult     []*AnalyzeResult
	}{
		{
			name: "pass if ubuntu >= 00.1.2",
			collectedContent: []collectedContent{
				{
					NodeName: "node1",
					Data: collectorToBytes(t, collect.HostOSInfo{
						Name:            "myhost",
						KernelVersion:   "5.4.0-1034-gcp",
						PlatformVersion: "00.1.2",
						Platform:        "ubuntu",
					}),
				},
			},
			outcomes: []*troubleshootv1beta2.Outcome{
				{
					Pass: &troubleshootv1beta2.SingleOutcome{
						When:    "ubuntu >= 00.1.2",
						Message: "supported distribution matches ubuntu >= 00.1.2",
					},
				},
				{
					Fail: &troubleshootv1beta2.SingleOutcome{
						Message: "unsupported distribution",
					},
				},
			},
			expectResult: []*AnalyzeResult{
				{
					Title:   "Host OS Info - Node node1",
					IsPass:  true,
					Message: "supported distribution matches ubuntu >= 00.1.2",
				},
			},
		},
		{
			name: "fail if ubuntu <= 11.04",
			collectedContent: []collectedContent{
				{
					NodeName: "node1",
					Data: collectorToBytes(t, collect.HostOSInfo{
						Name:            "myhost",
						KernelVersion:   "5.4.0-1034-gcp",
						PlatformVersion: "11.04",
						Platform:        "ubuntu",
					}),
				},
				{
					NodeName: "node2",
					Data: collectorToBytes(t, collect.HostOSInfo{
						Name:            "myhost",
						KernelVersion:   "5.4.0-1034-gcp",
						PlatformVersion: "11.04",
						Platform:        "ubuntu",
					}),
				},
			},
			outcomes: []*troubleshootv1beta2.Outcome{
				{
					Fail: &troubleshootv1beta2.SingleOutcome{
						When:    "ubuntu <= 11.04",
						Message: "unsupported ubuntu version 11.04",
					},
				},
				{
					Pass: &troubleshootv1beta2.SingleOutcome{
						Message: "supported distribution",
					},
				},
			},
			expectResult: []*AnalyzeResult{
				{
					Title:   "Host OS Info - Node node1",
					IsFail:  true,
					Message: "unsupported ubuntu version 11.04",
				},
				{
					Title:   "Host OS Info - Node node2",
					IsFail:  true,
					Message: "unsupported ubuntu version 11.04",
				},
			},
		},
		{
			name: "title does not include node name if empty",
			collectedContent: []collectedContent{
				{
					NodeName: "",
					Data: collectorToBytes(t, collect.HostOSInfo{
						Name:            "myhost",
						KernelVersion:   "5.4.0-1034-gcp",
						PlatformVersion: "20.04",
						Platform:        "ubuntu",
					}),
				},
			},
			outcomes: []*troubleshootv1beta2.Outcome{
				{
					Pass: &troubleshootv1beta2.SingleOutcome{
						When:    "ubuntu >= 20.04",
						Message: "supported distribution matches ubuntu >= 20.04",
					},
				},
				{
					Fail: &troubleshootv1beta2.SingleOutcome{
						Message: "unsupported distribution",
					},
				},
			},
			expectResult: []*AnalyzeResult{
				{
					Title:   "Host OS Info", // Ensuring the title does not include node name if it's empty
					IsPass:  true,
					Message: "supported distribution matches ubuntu >= 20.04",
				},
			},
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			a := AnalyzeHostOS{}
			// Call the new analyzeHostCollectorResults function with the test data
			result, err := analyzeHostCollectorResults(test.collectedContent, test.outcomes, a.CheckCondition, "Host OS Info")
			require.NoError(t, err)
			assert.Equal(t, test.expectResult, result)
		})
	}
}

func TestEvaluateOutcomes(t *testing.T) {
	tests := []struct {
		name           string
		outcomes       []*troubleshootv1beta2.Outcome
		checkCondition func(string, []byte) (bool, error)
		data           []byte
		expectedResult []*AnalyzeResult
	}{
		{
			name: "fail condition matches",
			outcomes: []*troubleshootv1beta2.Outcome{
				{
					Fail: &troubleshootv1beta2.SingleOutcome{
						When:    "failCondition",
						Message: "failure condition met",
					},
				},
			},
			checkCondition: func(when string, data []byte) (bool, error) {
				// Return true if the condition being checked matches "failCondition"
				return when == "failCondition", nil
			},
			data: []byte("someData"),
			expectedResult: []*AnalyzeResult{
				{
					Title:   "Test Title",
					IsFail:  true,
					Message: "failure condition met",
				},
			},
		},
		{
			name: "warn condition matches",
			outcomes: []*troubleshootv1beta2.Outcome{
				{
					Warn: &troubleshootv1beta2.SingleOutcome{
						When:    "warnCondition",
						Message: "warning condition met",
					},
				},
			},
			checkCondition: func(when string, data []byte) (bool, error) {
				// Return true if the condition being checked matches "warnCondition"
				return when == "warnCondition", nil
			},
			data: []byte("someData"),
			expectedResult: []*AnalyzeResult{
				{
					Title:   "Test Title",
					IsWarn:  true,
					Message: "warning condition met",
				},
			},
		},
		{
			name: "pass condition matches",
			outcomes: []*troubleshootv1beta2.Outcome{
				{
					Pass: &troubleshootv1beta2.SingleOutcome{
						When:    "passCondition",
						Message: "pass condition met",
					},
				},
			},
			checkCondition: func(when string, data []byte) (bool, error) {
				// Return true if the condition being checked matches "passCondition"
				return when == "passCondition", nil
			},
			data: []byte("someData"),
			expectedResult: []*AnalyzeResult{
				{
					Title:   "Test Title",
					IsPass:  true,
					Message: "pass condition met",
				},
			},
		},
		{
			name: "no condition matches",
			outcomes: []*troubleshootv1beta2.Outcome{
				{
					Fail: &troubleshootv1beta2.SingleOutcome{
						When:    "failCondition",
						Message: "failure condition met",
					},
					Warn: &troubleshootv1beta2.SingleOutcome{
						When:    "warnCondition",
						Message: "warning condition met",
					},
					Pass: &troubleshootv1beta2.SingleOutcome{
						When:    "passCondition",
						Message: "pass condition met",
					},
				},
			},
			checkCondition: func(when string, data []byte) (bool, error) {
				// Always return false to simulate no condition matching
				return false, nil
			},
			data:           []byte("someData"),
			expectedResult: nil, // No condition matches, so we expect no results
		},
		{
			name: "error in checkCondition",
			outcomes: []*troubleshootv1beta2.Outcome{
				{
					Fail: &troubleshootv1beta2.SingleOutcome{
						When:    "failCondition",
						Message: "failure condition met",
					},
				},
			},
			checkCondition: func(when string, data []byte) (bool, error) {
				// Simulate an error occurring during condition evaluation
				return false, errors.New("mock error")
			},
			data: []byte("someData"),
			expectedResult: []*AnalyzeResult{
				{
					Title:  "Test Title",
					IsFail: false, // Error occurred, so no success flag
				},
			},
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			result, err := evaluateOutcomes(test.outcomes, test.checkCondition, test.data, "Test Title")

			if test.name == "error in checkCondition" {
				require.Error(t, err)
			} else {
				require.NoError(t, err)
				assert.Equal(t, test.expectedResult, result)
			}
		})
	}
}
