package agent

import (
	"context"
	"errors"
	"fmt"
	"log/slog"
	"strconv"
	"strings"

	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/api"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/module/remote_development/agent/k8s"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/tool/logz"
	corev1 "k8s.io/api/core/v1"
	k8serrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime"
)

const (
	workspaceImagePullSecretNameLabel      = "workspaces.gitlab.com/upstream-image-pull-secret-name"
	workspaceImagePullSecretNamespaceLabel = "workspaces.gitlab.com/upstream-image-pull-secret-namespace"
)

type imagePullSecretsHandler func(ctx context.Context, agentKey api.AgentKey, namespace string, name string, imagePullSecrets []ImagePullSecret) error

func newImagePullSecretsHandler(logger *slog.Logger, client k8s.Client, secretInformer informer, tracker *imagePullSecretTracker) imagePullSecretsHandler {
	return func(ctx context.Context, agentKey api.AgentKey, namespace string, name string, imagePullSecrets []ImagePullSecret) error {
		for _, imagePullSecret := range imagePullSecrets {

			// Read informer if failed, defer to API
			upstreamSecretUnstructured, err := secretInformer.Get(imagePullSecret.Namespace, imagePullSecret.Name)
			if err != nil || upstreamSecretUnstructured == nil {
				upstreamSecretUnstructured, err = client.Get(ctx, secretGVR, imagePullSecret.Namespace, imagePullSecret.Name)
			}

			if err != nil {
				logger.Warn("Error fetching upstream image pull secret from secrets informer",
					logz.WorkspaceNamespace(namespace),
					logz.WorkspaceName(name),
					logz.K8sObjectNamespace(imagePullSecret.Namespace),
					logz.K8sObjectName(imagePullSecret.Name),
					logz.Error(err),
				)

				// Cleanup possibly deleted secret from the workspace namespace that the informer failed to/missed
				if k8serrors.IsNotFound(err) {
					tracker.delete(imagePullSecret.Namespace, imagePullSecret.Name, namespace)
					err = client.Delete(ctx, secretGVR, namespace, imagePullSecret.Name)
					if err != nil && !k8serrors.IsNotFound(err) {
						logger.Error("Error deleting secret",
							logz.K8sObjectNamespace(namespace),
							logz.K8sObjectName(name),
							logz.Error(err),
						)
					}
				}
				continue

			}

			var upstreamSecret *corev1.Secret
			err = runtime.DefaultUnstructuredConverter.FromUnstructured(upstreamSecretUnstructured.Object, &upstreamSecret)
			if err != nil {
				logger.Error("Error parsing upstream image pull secret from informer",
					logz.WorkspaceNamespace(namespace),
					logz.WorkspaceName(name),
					logz.K8sObjectNamespace(imagePullSecret.Namespace),
					logz.K8sObjectName(imagePullSecret.Name),
					logz.Error(err),
				)
				continue
			}

			secret := generateWorkspaceImagePullSecret(agentKey, namespace, upstreamSecret)
			_, err = client.UpdateOrCreate(ctx, secretGVR, namespace, secret, true)
			if err != nil {
				return fmt.Errorf("failed to clone upstream image pull secret %s in namespace %s: %w", upstreamSecret.Name, namespace, err)
			}
			tracker.set(imagePullSecret.Namespace, imagePullSecret.Name, namespace)
		}
		return nil
	}
}

func isK8sDefinedSecretType(u *unstructured.Unstructured) (bool, error) {
	secretType, found, err := unstructured.NestedString(u.Object, "type")
	if err != nil {
		return false, err
	}

	if !found {
		return false, errors.New("'type' field not present in secret")
	}

	return strings.HasPrefix(secretType, "kubernetes.io/"), nil
}

func generateWorkspaceImagePullSecret(agentKey api.AgentKey, targetNamespace string, upstreamSecret *corev1.Secret) *corev1.Secret {
	upstreamSecret = upstreamSecret.DeepCopy()
	if upstreamSecret.Labels == nil {
		upstreamSecret.Labels = make(map[string]string)
	}
	upstreamSecret.Labels[workspaceImagePullSecretNameLabel] = upstreamSecret.Name
	upstreamSecret.Labels[workspaceImagePullSecretNamespaceLabel] = upstreamSecret.Namespace
	upstreamSecret.Labels[api.AgentIDKey] = strconv.FormatInt(agentKey.ID, 10)

	// Clean to play well with possible Apply call on the secret resource
	delete(upstreamSecret.Annotations, "kubectl.kubernetes.io/last-applied-configuration")

	upstreamSecret.ObjectMeta = metav1.ObjectMeta{
		Name:        upstreamSecret.Name,
		Namespace:   targetNamespace,
		Labels:      upstreamSecret.Labels,
		Annotations: upstreamSecret.Annotations,
	}

	return upstreamSecret
}
