/*
 * Copyright (c) 2025 Huawei Technologies Co., Ltd.
 * openFuyao is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package controller

import (
	"fmt"

	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/util/rand"
	ctrl "sigs.k8s.io/controller-runtime"

	serverlessdbv1 "serverlessdb/api/v1"
)

func extractPgPort(podTemplate corev1.PodTemplateSpec) int32 {
	for _, container := range podTemplate.Spec.Containers {
		if container.Name != serverlessdbv1.ContainerNameCompute {
			continue
		}
		if port := findPgPort(container.Ports); port > 0 {
			return port
		}
	}
	return serverlessdbv1.DefaultPgPort
}

func findPgPort(ports []corev1.ContainerPort) int32 {
	for _, port := range ports {
		if port.Name == serverlessdbv1.PortNamePg {
			return port.ContainerPort
		}
	}
	return 0
}

func generateInstanceName(poolName string) string {
	return fmt.Sprintf("%s-%s", poolName, rand.String(5))
}

func buildPod(pool *serverlessdbv1.DBResourcePool, instance *serverlessdbv1.DBInstance, scheme *runtime.Scheme) (*corev1.Pod, error) {
	podSpec := pool.Spec.PodTemplate.Spec.DeepCopy()

	labels := map[string]string{}
	for k, v := range pool.Spec.PodTemplate.ObjectMeta.Labels {
		labels[k] = v
	}
	labels[serverlessdbv1.LabelKeyDBResourcePool] = pool.Name
	if appRef := instance.Labels[serverlessdbv1.LabelKeyAppRef]; appRef != "" {
		labels[serverlessdbv1.LabelKeyAppRef] = appRef
	}

	annotations := map[string]string{}
	for k, v := range pool.Spec.PodTemplate.ObjectMeta.Annotations {
		annotations[k] = v
	}

	pod := &corev1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			Name:        instance.Name,
			Namespace:   pool.Namespace,
			Labels:      labels,
			Annotations: annotations,
		},
		Spec: *podSpec,
	}

	applyCurrentResources(pod, instance.Status.CurrentResources)

	if err := ctrlSetControllerReference(instance, pod, scheme); err != nil {
		return nil, err
	}

	return pod, nil
}

// applyCurrentResources overwrites container resources on the pod with the
// last-known actual values from instance.Status.CurrentResources. This keeps
// resized resources across pod recreation instead of reverting to the pool
// template values. Containers are matched by name; empty snapshots leave the
// template spec untouched (covers the scaleUp path).
func applyCurrentResources(pod *corev1.Pod, res []serverlessdbv1.ContainerResources) {
	if len(res) == 0 {
		return
	}
	for i := range pod.Spec.Containers {
		c := &pod.Spec.Containers[i]
		var matched *serverlessdbv1.ContainerResources
		for j := range res {
			if res[j].Name == c.Name {
				matched = &res[j]
				break
			}
		}
		if matched == nil {
			continue
		}
		if c.Resources.Requests == nil {
			c.Resources.Requests = corev1.ResourceList{}
		}
		if c.Resources.Limits == nil {
			c.Resources.Limits = corev1.ResourceList{}
		}
		if q, ok := parseQuantity(matched.Resources.Cpu); ok {
			c.Resources.Requests[corev1.ResourceCPU] = q
		}
		if q, ok := parseQuantity(matched.Resources.Memory); ok {
			c.Resources.Requests[corev1.ResourceMemory] = q
		}
		if q, ok := parseQuantity(matched.Resources.CpuLimit); ok {
			c.Resources.Limits[corev1.ResourceCPU] = q
		}
		if q, ok := parseQuantity(matched.Resources.MemoryLimit); ok {
			c.Resources.Limits[corev1.ResourceMemory] = q
		}
	}
}

func parseQuantity(s string) (resource.Quantity, bool) {
	if s == "" {
		return resource.Quantity{}, false
	}
	q, err := resource.ParseQuantity(s)
	if err != nil {
		return resource.Quantity{}, false
	}
	return q, true
}

func ctrlSetControllerReference(owner, controlled metav1.Object, scheme *runtime.Scheme) error {
	return ctrl.SetControllerReference(owner, controlled, scheme)
}