/*
Copyright 2023 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package kubectlnfd

import (
	"fmt"
	"os"
	"strings"

	"sigs.k8s.io/yaml"

	corev1 "k8s.io/api/core/v1"

	nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/api/nfd/v1alpha1"
	"sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/nodefeaturerule"
	"sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/validate"
)

func DryRun(nodefeaturerulepath, nodefeaturepath string) []error {
	var errs []error
	nfr := nfdv1alpha1.NodeFeatureRule{}
	nf := nfdv1alpha1.NodeFeature{}

	nfrFile, err := os.ReadFile(nodefeaturerulepath)
	if err != nil {
		return []error{fmt.Errorf("error reading NodeFeatureRule file: %w", err)}
	}

	err = yaml.Unmarshal(nfrFile, &nfr)
	if err != nil {
		return []error{fmt.Errorf("error parsing NodeFeatureRule: %w", err)}
	}

	nfFile, err := os.ReadFile(nodefeaturepath)
	if err != nil {
		return []error{fmt.Errorf("error reading NodeFeatureRule file: %w", err)}
	}

	err = yaml.Unmarshal(nfFile, &nf)
	if err != nil {
		return []error{fmt.Errorf("error parsing NodeFeatureRule: %w", err)}
	}

	errs = append(errs, processNodeFeatureRule(nfr, nf.Spec)...)

	return errs
}

func processNodeFeatureRule(nodeFeatureRule nfdv1alpha1.NodeFeatureRule, nodeFeature nfdv1alpha1.NodeFeatureSpec) []error {
	var errs []error
	var taints []corev1.Taint

	extendedResources := make(map[string]string)
	labels := make(map[string]string)
	annotations := make(map[string]string)

	for _, rule := range nodeFeatureRule.Spec.Rules {
		fmt.Println("Processing rule: ", rule.Name)
		ruleOut, err := nodefeaturerule.Execute(&rule, &nodeFeature.Features)
		if err != nil {
			errs = append(errs, fmt.Errorf("failed to process rule: %q - %w", rule.Name, err))
			continue
		}
		// taints
		taints = append(taints, ruleOut.Taints...)
		// labels
		for k, v := range ruleOut.Labels {
			// Dynamic Value
			if strings.HasPrefix(v, "@") {
				dvalue, err := getDynamicValue(v, &nodeFeature.Features)
				if err != nil {
					errs = append(errs, fmt.Errorf("failed to get dynamic value for label %q: %w", k, err))
					continue
				}
				labels[k] = dvalue
				continue
			}
			labels[k] = v
		}
		// extended resources
		for k, v := range ruleOut.ExtendedResources {
			// Dynamic Value
			if strings.HasPrefix(v, "@") {
				dvalue, err := getDynamicValue(v, &nodeFeature.Features)
				if err != nil {
					errs = append(errs, fmt.Errorf("failed to get dynamic value for extendedResource %q: %w", k, err))
					continue
				}
				extendedResources[k] = dvalue
				continue
			}
			extendedResources[k] = v
		}
		// annotations
		for k, v := range ruleOut.Annotations {
			annotations[k] = v
		}
	}

	if len(taints) > 0 {
		taintValidation := validate.Taints(taints)
		fmt.Println("***\tTaints\t***")
		for _, taint := range taints {
			fmt.Println(taint)
		}
		if len(taintValidation) > 0 {
			fmt.Println("\t-Validation errors-")
			for _, err := range taintValidation {
				fmt.Println(err)
			}
		}
	}

	if len(labels) > 0 {
		labelValidation := validate.Labels(labels)
		fmt.Println("***\tLabels\t***")
		for k, v := range labels {
			fmt.Printf("%s=%s\n", k, v)
		}
		if len(labelValidation) > 0 {
			fmt.Println("\t-Validation errors-")
			for _, err := range labelValidation {
				fmt.Println(err)
			}
		}
	}

	if len(extendedResources) > 0 {
		resourceValidation := processExtendedResources(extendedResources, nodeFeature)
		fmt.Println("***\tExtended Resources\t***")
		for k, v := range extendedResources {
			fmt.Printf("%s=%s\n", k, v)
		}
		if len(resourceValidation) > 0 {
			fmt.Println("\t-Validation errors-")
			for _, err := range resourceValidation {
				fmt.Println(err)
			}
		}
	}

	if len(annotations) > 0 {
		annotationsValidation := validate.Annotations(annotations)
		fmt.Println("***\tAnnotations\t***")
		for k, v := range annotations {
			fmt.Printf("%s=%s\n", k, v)
		}
		if len(annotationsValidation) > 0 {
			fmt.Println("\t-Validation errors-")
			for _, err := range annotationsValidation {
				fmt.Println(err)
			}
		}
	}

	return errs
}

func processExtendedResources(extendedResources map[string]string, nodeFeature nfdv1alpha1.NodeFeatureSpec) []error {
	var errs []error
	return append(errs, validate.ExtendedResources(extendedResources)...)
}

func getDynamicValue(value string, features *nfdv1alpha1.Features) (string, error) {
	// value is a string in the form of attribute.featureset.elements
	split := strings.SplitN(value[1:], ".", 3)
	if len(split) != 3 {
		return "", fmt.Errorf("value %s is not in the form of '@domain.feature.element'", value)
	}
	featureName := split[0] + "." + split[1]
	elementName := split[2]
	attrFeatureSet, ok := features.Attributes[featureName]
	if !ok {
		return "", fmt.Errorf("feature %s not found", featureName)
	}
	element, ok := attrFeatureSet.Elements[elementName]
	if !ok {
		return "", fmt.Errorf("element %s not found on feature %s", elementName, featureName)
	}
	return element, nil
}