package preflight

import (
	"reflect"

	"github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
	"github.com/replicatedhq/troubleshoot/pkg/loader"
	"github.com/replicatedhq/troubleshoot/pkg/multitype"
	"github.com/replicatedhq/troubleshoot/pkg/types"
)

// validatePreflight validates the preflight spec and returns a warning if there is any
func validatePreflight(kinds *loader.TroubleshootKinds) *types.ExitCodeWarning {

	if len(kinds.PreflightsV1Beta2) == 0 && len(kinds.HostPreflightsV1Beta2) == 0 {
		return types.NewExitCodeWarning("no preflight or host preflight spec was found")
	}

	for _, spec := range kinds.PreflightsV1Beta2 {
		warning := validatePreflightSpecItems(spec.Spec.Collectors, spec.Spec.Analyzers)
		if warning != nil {
			return warning
		}
	}

	for _, spec := range kinds.HostPreflightsV1Beta2 {
		warning := validateHostPreflightSpecItems(spec.Spec.Collectors, spec.Spec.Analyzers)
		if warning != nil {
			return warning
		}
	}

	return nil
}

// validatePreflightSpecItems validates the preflight spec items and returns a warning if there are any
// clusterResources or clusterInfo collectors added automatically to the preflight spec. It cannot be excluded
func validatePreflightSpecItems(collectors []*v1beta2.Collect, analyzers []*v1beta2.Analyze) *types.ExitCodeWarning {
	var numberOfExcludedCollectors, numberOfExcludedAnalyzers int
	var numberOfExcludedDefaultCollectors int
	numberOfCollectors := len(collectors) + 2
	numberOfAnalyzers := len(analyzers)

	if numberOfCollectors >= 0 {
		collectorsInterface := make([]interface{}, len(collectors))
		for i, v := range collectors {
			if v.ClusterInfo != nil || v.ClusterResources != nil {
				numberOfExcludedDefaultCollectors++
			}
			collectorsInterface[i] = v
		}

		numberOfExcludedCollectors += countExcludedItems(collectorsInterface)

		if numberOfExcludedCollectors+numberOfExcludedDefaultCollectors == numberOfCollectors {
			return types.NewExitCodeWarning("All collectors were excluded by the applied values")
		}
	}

	// if there are no analyzers, return a warning
	// else check if all analyzers are excluded
	if numberOfAnalyzers == 0 {
		return types.NewExitCodeWarning("No analyzers found")
	} else {
		analyzersInterface := make([]interface{}, len(analyzers))
		for i, v := range analyzers {
			analyzersInterface[i] = v
		}

		numberOfExcludedAnalyzers = countExcludedItems(analyzersInterface)

		if numberOfExcludedAnalyzers == numberOfAnalyzers {
			return types.NewExitCodeWarning("All analyzers were excluded by the applied values")
		}
	}
	return nil
}

// validateHostPreflightSpecItems validates the host preflight spec items and returns a warning if there is any
// no collectors are added or excluded automatically to the host preflight spec
func validateHostPreflightSpecItems(collectors []*v1beta2.HostCollect, analyzers []*v1beta2.HostAnalyze) *types.ExitCodeWarning {
	var numberOfExcludedCollectors, numberOfExcludedAnalyzers int
	numberOfCollectors := len(collectors)
	numberOfAnalyzers := len(analyzers)

	// if there are no collectors, return a warning
	if numberOfCollectors == 0 {
		return types.NewExitCodeWarning("No collectors found")
	}

	// if there are no analyzers, return a warning
	if numberOfAnalyzers == 0 {
		return types.NewExitCodeWarning("No analyzers found")
	}

	collectorsInterface := make([]interface{}, len(collectors))
	for i, v := range collectors {
		collectorsInterface[i] = v
	}

	analyzersInterface := make([]interface{}, len(analyzers))
	for i, v := range analyzers {
		analyzersInterface[i] = v
	}

	numberOfExcludedCollectors = countExcludedItems(collectorsInterface)
	numberOfExcludedAnalyzers = countExcludedItems(analyzersInterface)

	if numberOfExcludedCollectors == numberOfCollectors {
		return types.NewExitCodeWarning("All collectors were excluded by the applied values")
	}

	if numberOfExcludedAnalyzers == numberOfAnalyzers {
		return types.NewExitCodeWarning("All analyzers were excluded by the applied values")
	}

	return nil
}

// countExcludedItems counts and returns the number of excluded items in the given items slice.
// Items are assumed to be structures that may have an "Exclude" field as bool
// If the "Exclude" field is true, the item is considered excluded.
func countExcludedItems(items []interface{}) int {
	numberOfExcludedItems := 0
	for _, item := range items {
		itemElem := reflect.ValueOf(item).Elem()

		// Loop over all fields of the current item.
		for i := 0; i < itemElem.NumField(); i++ {
			// Get the value of the current field.
			itemValue := itemElem.Field(i)
			// If the current field is a pointer to a struct, check if it has an "Exclude" field.
			if !itemValue.IsNil() {
				elem := itemValue.Elem()
				if elem.Kind() == reflect.Struct {
					// Look for a field named "Exclude" in the struct.
					excludeField := elem.FieldByName("Exclude")
					if excludeField.IsValid() {
						// Try to get the field's value as a *multitype.BoolOrString.
						excludeValue, ok := excludeField.Interface().(*multitype.BoolOrString)
						// If the field's value was successfully obtained and is not nil, and the value is true
						if ok && excludeValue != nil {
							if excludeValue.BoolOrDefaultFalse() {
								numberOfExcludedItems++
							}
						}
					}
				}
			}
		}
	}
	return numberOfExcludedItems
}
