// Licensed to Apache Software Foundation (ASF) under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Apache Software Foundation (ASF) licenses this file to you 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 injector

import (
	"encoding/json"
	"fmt"
	"net"
	"reflect"
	"regexp"
	"runtime"
	"strings"

	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/util/sets"
)

// AnnotationValidateFunc is the type of validate function
type AnnotationValidateFunc func(annotation, value string) error

var (
	//AnnotationValidateFuncs define all validate functions
	AnnotationValidateFuncs = []AnnotationValidateFunc{
		ValidateServiceName,
		ValidateBackendServices,
		ValidateIPv4OrHostname,
		ValidateResourceRequirements,
	}
)

const (
	// CPU is in cores (100m = .1 cores)
	CPU string = "cpu"
	// Memory is in bytes (100Gi = 100GiB = 100 * 1024 * 1024 * 1024)
	Memory string = "memory"
	// EphemeralStorage is in bytes (100Gi = 100GiB = 100 * 1024 * 1024 * 1024)
	EphemeralStorage string = "ephemeral-storage"
	// HugePagesPrefix is huge pages prefix
	HugePagesPrefix string = "hugepages-"
)

var standardContainerResources = sets.NewString(
	CPU,
	Memory,
	EphemeralStorage,
)

// FindValidateFunc is find the validate function for an annotation
func FindValidateFunc(funcName string) AnnotationValidateFunc {
	for _, f := range AnnotationValidateFuncs {
		// extract the function name into a string , it will be like following
		// github.com/apache/skywalking-swck/operator/pkg/operator/injector.ValidateBool
		fname := runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
		// get real validate function name in injector
		index := strings.LastIndex(fname, ".")
		funcname := fname[index+1:]

		if funcname == funcName {
			return f
		}
	}
	return nil
}

// ValidateServiceName validates the ServiceName is nil or not
func ValidateServiceName(annotation, value string) error {
	if value == "" {
		return fmt.Errorf("%s error:the service name is nil", annotation)
	}
	return nil
}

// ValidateBackendServices validates an annotation's value is valid backend services
func ValidateBackendServices(annotation, value string) error {
	services := []string{value}
	if strings.Contains(value, ",") {
		services = strings.Split(value, ",")
	}
	allEmpty := true
	for _, service := range services {
		service = strings.TrimSpace(service)
		if service == "" {
			continue
		}
		allEmpty = false
		err := ValidateIPv4OrHostname(annotation, service)
		if err != nil {
			return err
		}
	}
	if allEmpty {
		return fmt.Errorf("%s error:every service is nil", annotation)
	}
	return nil
}

// ValidateIPv4OrHostname validates an annotation's value is valid ipv4 or hostname
func ValidateIPv4OrHostname(annotation, service string) error {
	service = strings.TrimSpace(service)
	colonIndex := strings.LastIndex(service, ":")
	host := service
	if colonIndex != -1 {
		host = service[:colonIndex]
	}
	if host == "" {
		return fmt.Errorf("%s error:the service name is nil", annotation)
	}
	ip := net.ParseIP(host)
	if ip != nil && ip.To4() != nil {
		return nil
	}

	match, err := regexp.MatchString(`^([a-zA-Z0-9][a-zA-Z0-9_-]{0,62})(\.[a-zA-Z0-9_][a-zA-Z0-9_-]{0,62})*?$`,
		host)
	if err != nil {
		return fmt.Errorf("%s error:%s", annotation, err.Error())
	}
	if !match {
		return fmt.Errorf("%s=%s error:not a valid ipv4 or hostname", annotation, host)
	}

	return nil
}

// ValidateResourceRequirements validates the resource requirement
func ValidateResourceRequirements(annotation, value string) error {
	if value == "nil" {
		return nil
	}

	resource := make(corev1.ResourceList)
	err := json.Unmarshal([]byte(value), &resource)
	if err != nil {
		return fmt.Errorf("%s unmarshal error:%s", annotation, err.Error())
	}

	for resourceName, quantity := range resource {
		//validate resource name
		if !standardContainerResources.Has(string(resourceName)) && !strings.HasPrefix(string(resourceName), HugePagesPrefix) {
			return fmt.Errorf("%s error:%s isn't a standard resource type", annotation, string(resourceName))
		}

		//validate resource quantity value
		if quantity.MilliValue() <= int64(0) {
			return fmt.Errorf("%s error:%d must be greater than 0", annotation, quantity.MilliValue())
		}
	}

	return nil
}
