package agent

import (
	"context"
	"fmt"
	"time"

	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/api"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/module/modagentk"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/module/remote_development"
	"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/retry"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/fields"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/client-go/dynamic"
	"k8s.io/client-go/dynamic/dynamicinformer"
	"k8s.io/client-go/rest"
)

const (
	interval      = 10 * time.Second
	initBackoff   = 10 * time.Second
	maxBackoff    = time.Minute
	resetDuration = 2 * time.Minute
	backoffFactor = 2.0
	jitter        = 5.0

	informerResyncDuration       = 5 * time.Minute
	prerequisitesPollingInterval = 1 * time.Hour
)

var (
	deploymentGVR = schema.GroupVersionResource{
		Group:    "apps",
		Version:  "v1",
		Resource: "deployments",
	}
	configMapGVR = schema.GroupVersionResource{
		Group:    "",
		Version:  "v1",
		Resource: "configmaps",
	}
	secretGVR = schema.GroupVersionResource{
		Group:    "",
		Version:  "v1",
		Resource: "secrets",
	}
	namespaceGVR = schema.GroupVersionResource{
		Group:    "",
		Version:  "v1",
		Resource: "namespaces",
	}
)

type WorkspacePrerequisites struct {
	SharedNamespace string `json:"shared_namespace,omitempty"`
}

type Factory struct {
}

func (f *Factory) IsProducingLeaderModules() bool {
	return true
}

func (f *Factory) New(config *modagentk.Config) (modagentk.Module, error) {
	restConfig, err := config.K8sUtilFactory.ToRESTConfig()
	if err != nil {
		return nil, err
	}

	restConfig = rest.CopyConfig(restConfig)
	restConfig.UserAgent = config.AgentNameVersion

	client, err := dynamic.NewForConfig(restConfig)
	if err != nil {
		return nil, err
	}

	k8sClient, err := k8s.New(config.Log, config.K8sUtilFactory)
	if err != nil {
		return nil, err
	}

	pollFactory := retry.NewPollConfigFactory(interval, retry.NewExponentialBackoffFactory(
		initBackoff,
		maxBackoff,
		resetDuration,
		backoffFactor,
		jitter,
	))

	return &module{
		log: config.Log,
		api: config.API,
		prerequisitesPollCfg: retry.NewPollConfigFactory(prerequisitesPollingInterval, retry.NewExponentialBackoffFactory(
			initBackoff,
			prerequisitesPollingInterval*2,
			resetDuration,
			backoffFactor,
			jitter,
		)),
		reconcilerFactory: func(ctx context.Context, namespace string) (remoteDevReconciler, error) {
			agentKey, err := config.API.GetAgentKey(ctx)
			if err != nil {
				return nil, err
			}

			deploymentLabelSelector := fmt.Sprintf("%s=%d", api.AgentIDKey, agentKey.ID)
			deploymentInformerFactory := dynamicinformer.NewFilteredDynamicSharedInformerFactory(
				client,
				informerResyncDuration,
				namespace,
				func(opts *metav1.ListOptions) {
					opts.LabelSelector = deploymentLabelSelector
				},
			).ForResource(deploymentGVR)

			deploymentInformer := newK8sInformer(
				config.Log,
				deploymentInformerFactory.Informer(),
				deploymentInformerFactory.Lister(),
			)
			_, err = deploymentInformer.informer.AddEventHandler(deploymentEventHandler(config.Log))
			if err != nil {
				return nil, err
			}

			// Filter out all kubernetes secret types https://kubernetes.io/docs/concepts/configuration/secret/#secret-types,
			// leaving access only to types kubernetes.io/dockercfg and kubernetes.io/dockerconfigjson.
			secretFieldSelector := fields.AndSelectors(
				fields.OneTermNotEqualSelector("type", string(corev1.SecretTypeOpaque)),
				fields.OneTermNotEqualSelector("type", string(corev1.SecretTypeServiceAccountToken)),
				fields.OneTermNotEqualSelector("type", string(corev1.SecretTypeBasicAuth)),
				fields.OneTermNotEqualSelector("type", string(corev1.SecretTypeSSHAuth)),
				fields.OneTermNotEqualSelector("type", string(corev1.SecretTypeTLS)),
				fields.OneTermNotEqualSelector("type", string(corev1.SecretTypeBootstrapToken)),
			).String()

			imagePullSecretsTracker := newImagePullSecretTracker()
			secretInformerFactory := dynamicinformer.NewFilteredDynamicSharedInformerFactory(
				client,
				informerResyncDuration,
				namespace,
				func(opts *metav1.ListOptions) {
					opts.FieldSelector = secretFieldSelector
				},
			).ForResource(secretGVR)

			secretInformer := newK8sInformer(
				config.Log,
				secretInformerFactory.Informer(),
				secretInformerFactory.Lister(),
			)
			_, err = secretInformer.informer.AddEventHandler(
				secretEventHandler(
					config.Log,
					ctx,
					imagePullSecretsTracker,
					k8sClient,
					agentKey,
				),
			)
			if err != nil {
				return nil, err
			}

			var namespaceHandler namespaceHandler
			if namespace == corev1.NamespaceAll {
				namespaceHandler = newUniqueNamespaceHandler(k8sClient, agentKey)
			} else {
				namespaceHandler = newSharedNamespaceHandler(k8sClient)
			}

			r := &reconciler{
				log:                    config.Log,
				agentKey:               agentKey,
				api:                    config.API,
				pollConfig:             pollFactory,
				stateTracker:           newPersistedStateTracker(),
				terminationTracker:     newTerminationTracker(),
				deploymentInformer:     deploymentInformer,
				secretInformer:         secretInformer,
				k8sClient:              k8sClient,
				imagePullSecretHandler: newImagePullSecretsHandler(config.Log, k8sClient, secretInformer, imagePullSecretsTracker),
				errDetailsTracker:      newErrorDetailsTracker(),
				namespaceHandler:       namespaceHandler,
			}

			err = r.deploymentInformer.Start(ctx)
			if err != nil {
				return nil, err
			}

			err = r.secretInformer.Start(ctx)
			if err != nil {
				return nil, err
			}

			return r, nil
		},
	}, nil
}

func (f *Factory) Name() string {
	return remote_development.ModuleName
}
