package agentbuild

import (
	"bytes"
	"encoding/json"
	"fmt"
	"strings"
	"text/template"

	vmoperatorv1beta1 "github.com/VictoriaMetrics/operator/api/operator/v1beta1"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/yaml"

	"github.com/tidbcloud/observability/agent-control-server/pkg/config"
	"github.com/tidbcloud/observability/agent-control-server/pkg/consts"
	"github.com/tidbcloud/observability/agent-control-server/pkg/o11y"
	"github.com/tidbcloud/observability/agent-control-server/pkg/util"
)

const (
	defaultBufferReservedSize = "1Gi"
)

// BuildVMAgent builds a VMAgent object based on the provided template and parameters.
//
// It merges the template with values and applies resource requirements, storage spec, and extra args.
func BuildVMAgent(
	tmpl *template.Template,
	resources *corev1.ResourceRequirements,
	storage *vmoperatorv1beta1.StorageSpec,
	args,
	values map[string]string,
) (*vmoperatorv1beta1.VMAgent, error) {
	return BuildVMAgentWithPodLabels(tmpl, resources, storage, nil, nil, args, values, nil)
}

// BuildVMAgentWithPodLabels builds a VMAgent object with additional pod labels.
//
// It merges the template with values, applies resource requirements, storage spec, extra args, and pod labels.
func BuildVMAgentWithPodLabels(
	tmpl *template.Template,
	resources *corev1.ResourceRequirements,
	storage *vmoperatorv1beta1.StorageSpec,
	affinity *corev1.Affinity,
	tolerations []corev1.Toleration,
	args,
	values,
	podLabels map[string]string,
) (*vmoperatorv1beta1.VMAgent, error) {
	buf := bytes.NewBuffer(nil)
	if err := tmpl.Execute(buf, values); err != nil {
		return nil, fmt.Errorf("failed to execute vmagent template: %w", err)
	}
	var v vmoperatorv1beta1.VMAgent
	if err := yaml.Unmarshal(buf.Bytes(), &v); err != nil {
		return nil, fmt.Errorf("failed to unmarshal vmagent: %w", err)
	}

	if podLabels != nil {
		if v.Spec.PodMetadata == nil {
			v.Spec.PodMetadata = &vmoperatorv1beta1.EmbeddedObjectMetadata{
				Labels: map[string]string{},
			}
		}
		for name, value := range podLabels {
			if _, ok := v.Spec.PodMetadata.Labels[name]; !ok {
				v.Spec.PodMetadata.Labels[name] = value
			}
		}
	}

	if affinity != nil {
		v.Spec.Affinity = affinity.DeepCopy()
	}
	if len(tolerations) > 0 {
		v.Spec.Tolerations = tolerations
	}

	v.Spec.Resources = *resources.DeepCopy()
	v.Spec.StatefulMode = true
	v.Spec.StatefulStorage = storage.DeepCopy()
	v.Spec.ExtraArgs = map[string]string{}
	for k, arg := range args {
		v.Spec.ExtraArgs[k] = arg
	}
	// Calculate the optimal value of remoteWrite.maxDiskUsagePerURL based on pv size and the number of remoteWriteUrls.
	pvSize := storage.VolumeClaimTemplate.Spec.Resources.Requests.Storage().DeepCopy()
	maxDiskUsagePerURL, err := util.CalcVmagentMaxDiskUsagePerURLFromPVSizeTyped(&pvSize, 1, defaultBufferReservedSize)
	if err != nil {
		return nil, fmt.Errorf("failed to calculate vmagent max disk usage per url: %w", err)
	}
	v.Spec.ExtraArgs["remoteWrite.maxDiskUsagePerURL"] = maxDiskUsagePerURL
	return &v, nil
}

// BuildVMPodScrape builds a VMPodScrape object based on the provided template and values.
//
// It executes the template with the given values and unmarshals the result into a VMPodScrape object.
func BuildVMPodScrape(tmpl *template.Template, values map[string]string) (*vmoperatorv1beta1.VMPodScrape, error) {
	buf := bytes.NewBuffer(nil)
	err := tmpl.Execute(buf, values)
	if err != nil {
		return nil, fmt.Errorf("failed to execute vmpodscrape template: %w", err)
	}

	var v vmoperatorv1beta1.VMPodScrape
	if err := yaml.Unmarshal(buf.Bytes(), &v); err != nil {
		return nil, fmt.Errorf("failed to unmarshal vmpodscrape: %w", err)
	}
	return &v, nil
}

// BuildVectorValues builds the values string for Vector based on the provided template and values.
//
// It executes the template with the given values and returns the resulting string.
func BuildVectorValues(tmpl *template.Template, values map[string]string) (string, error) {
	buf := bytes.NewBuffer(nil)
	err := tmpl.Execute(buf, values)
	if err != nil {
		return "", fmt.Errorf("failed to execute vector values template: %w", err)
	}
	return buf.String(), nil
}

// BuildRemoteWriteURL builds the remote write URL for the specified vendor and agent.
//
// It constructs the URL based on the vendor type (AWS, GCP, Azure) and agent annotations.
func BuildRemoteWriteURL(vendor string, agent client.Object) string {
	o11yID := util.GetAnnotation(agent, consts.AnnotationKeyO11yID)
	dnsName := util.GetAnnotation(agent, consts.AnnotationKeyO11yServiceDNSName)
	builder := o11y.NewWriteURLBuilder(dnsName)
	switch vendor {
	case consts.VendorTypeAWS:
		return builder.ForAWS().BuildRemoteWrite(o11yID)
	case consts.VendorTypeGCP:
		endpointName := util.GetAnnotation(agent, consts.AnnotationKeyO11ySvcInterfaceEndpointName)
		return builder.ForGCP(endpointName).BuildRemoteWrite(o11yID)
	case consts.VendorTypeAzure:
		return builder.ForAzure().BuildRemoteWrite(o11yID)
	default:
		return ""
	}
}

// BuildVectorResources updates the resources for the Vector agent based on annotations.
//
// It parses CPU and memory requests/limits from annotations and returns the resources as a JSON string.
func BuildVectorResources(agent client.Object, resources *corev1.ResourceRequirements) (string, error) {
	if cpuRequest := util.GetAnnotation(agent, consts.AnnotationKeyO11yVectorCPURequest); cpuRequest != "" {
		cpuRequestQ, err := resource.ParseQuantity(cpuRequest)
		if err != nil {
			return "", err
		}
		resources.Requests["cpu"] = cpuRequestQ
	}
	if cpuLimit := util.GetAnnotation(agent, consts.AnnotationKeyO11yVectorCPULimit); cpuLimit != "" {
		cpuLimitQ, err := resource.ParseQuantity(cpuLimit)
		if err != nil {
			return "", err
		}
		resources.Limits["cpu"] = cpuLimitQ
	}

	if memRequest := util.GetAnnotation(agent, consts.AnnotationKeyO11yVectorMemoryRequest); memRequest != "" {
		memRequestQ, err := resource.ParseQuantity(memRequest)
		if err != nil {
			return "", err
		}
		resources.Requests["memory"] = memRequestQ
	}
	if memLimit := util.GetAnnotation(agent, consts.AnnotationKeyO11yVectorMemoryLimit); memLimit != "" {
		memLimitQ, err := resource.ParseQuantity(memLimit)
		if err != nil {
			return "", err
		}
		resources.Limits["memory"] = memLimitQ
	}

	s, _ := json.Marshal(resources)
	return string(s), nil
}

// BuildVMAgentResources updates the resources for the VMAgent based on annotations.
//
// It parses CPU and memory requests/limits from annotations and applies them to the resources.
func BuildVMAgentResources(agent client.Object, resources *corev1.ResourceRequirements) error {
	if cpuRequest := util.GetAnnotation(agent, consts.AnnotationKeyO11yVMAgentCPURequest); cpuRequest != "" {
		cpuRequestQ, err := resource.ParseQuantity(cpuRequest)
		if err != nil {
			return err
		}
		resources.Requests["cpu"] = cpuRequestQ
	}
	if cpuLimit := util.GetAnnotation(agent, consts.AnnotationKeyO11yVMAgentCPULimit); cpuLimit != "" {
		cpuLimitQ, err := resource.ParseQuantity(cpuLimit)
		if err != nil {
			return err
		}
		resources.Limits["cpu"] = cpuLimitQ
	}

	if memRequest := util.GetAnnotation(agent, consts.AnnotationKeyO11yVMAgentMemoryRequest); memRequest != "" {
		memRequestQ, err := resource.ParseQuantity(memRequest)
		if err != nil {
			return err
		}
		resources.Requests["memory"] = memRequestQ
	}
	if memLimit := util.GetAnnotation(agent, consts.AnnotationKeyO11yVMAgentMemoryLimit); memLimit != "" {
		memLimitQ, err := resource.ParseQuantity(memLimit)
		if err != nil {
			return err
		}
		resources.Limits["memory"] = memLimitQ
	}

	return nil
}

// BuildVMAgentStorage updates the storage size for VMAgent based on annotations.
//
// It parses the storage size from annotations and applies it to the storage spec.
func BuildVMAgentStorage(agent client.Object, storage *vmoperatorv1beta1.StorageSpec) error {
	if storageSize := util.GetAnnotation(agent, consts.AnnotationKeyO11yVMAgentStorageSize); storageSize != "" {
		storageSizeQ, err := resource.ParseQuantity(storageSize)
		if err != nil {
			return err
		}
		if storage.VolumeClaimTemplate.Spec.Resources.Requests == nil {
			storage.VolumeClaimTemplate.Spec.Resources.Requests = make(corev1.ResourceList)
		}
		storage.VolumeClaimTemplate.Spec.Resources.Requests[corev1.ResourceStorage] = storageSizeQ
	}
	return nil
}

// BuildVectorImage returns the Vector image repository and tag based on annotations.
//
// It returns the annotation override if present, otherwise splits the provided image string.
func BuildVectorImage(agent client.Object, image string) (string, string, error) {
	if annoImage := util.GetAnnotation(agent, consts.AnnotationKeyO11yVectorImage); annoImage != "" {
		image = annoImage
	}
	return splitImageRepoTag(image)
}

// BuildVMAgentImage returns the VMAgent image repository and tag based on annotations.
//
// It returns the annotation override if present, otherwise splits the provided image string.
func BuildVMAgentImage(agent client.Object, image string) (string, string, error) {
	if annoImage := util.GetAnnotation(agent, consts.AnnotationKeyO11yVMAgentImage); annoImage != "" {
		image = annoImage
	}
	return splitImageRepoTag(image)
}

func splitImageRepoTag(image string) (string, string, error) {
	arr := strings.Split(image, ":")
	if len(arr) != 2 {
		return "", "", fmt.Errorf("invalid image format: %s", image)
	}
	return arr[0], arr[1], nil
}

// BuildVectorTemplate returns the Vector template based on annotations.
//
// It returns the annotation override if present, otherwise returns the default template.
func BuildVectorTemplate(agent client.Object, tmpls config.Templates, configTemplate string) (*template.Template, error) {
	return buildTemplate(consts.AnnotationKeyO11yVectorConfigTemplate, agent, tmpls, configTemplate)
}

// BuildVMAgentTemplate returns the VMAgent template based on annotations.
//
// It returns the annotation override if present, otherwise returns the default template.
func BuildVMAgentTemplate(agent client.Object, tmpls config.Templates, configTemplate string) (*template.Template, error) {
	return buildTemplate(consts.AnnotationKeyO11yVMAgentConfigTemplate, agent, tmpls, configTemplate)
}

func buildTemplate(annoKey string, agent client.Object, tmpls config.Templates, configTemplate string) (*template.Template, error) {
	if annoConfigTemplate := util.GetAnnotation(agent, annoKey); annoConfigTemplate != "" {
		configTemplate = annoConfigTemplate
	}
	tmpl, ok := tmpls[configTemplate]
	if !ok {
		return nil, fmt.Errorf("config template '%s' not found", configTemplate)
	}
	return tmpl, nil
}
