/*
 * Licensed to the 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.
 * The 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 inspector

import (
	"context"
	"crypto/tls"
	"fmt"
	"net/http"

	"k8s.io/client-go/informers"
	"k8s.io/client-go/kubernetes"
	appslisters "k8s.io/client-go/listers/apps/v1"
	auotscalinglisters "k8s.io/client-go/listers/autoscaling/v2"
	corelisters "k8s.io/client-go/listers/core/v1"
	"k8s.io/client-go/tools/cache"
	"k8s.io/klog/v2"
	"sigs.k8s.io/controller-runtime/pkg/manager"

	"github.com/apache/incubator-uniffle/deploy/kubernetes/operator/pkg/generated/clientset/versioned"
	"github.com/apache/incubator-uniffle/deploy/kubernetes/operator/pkg/generated/informers/externalversions"
	"github.com/apache/incubator-uniffle/deploy/kubernetes/operator/pkg/generated/listers/uniffle/v1alpha1"
	"github.com/apache/incubator-uniffle/deploy/kubernetes/operator/pkg/utils"
	"github.com/apache/incubator-uniffle/deploy/kubernetes/operator/pkg/webhook/config"
	webhookconstants "github.com/apache/incubator-uniffle/deploy/kubernetes/operator/pkg/webhook/constants"
	"github.com/apache/incubator-uniffle/deploy/kubernetes/operator/pkg/webhook/util"
)

var _ Inspector = &inspector{}

// Inspector intercepts the request and checks whether the pod can be deleted.
type Inspector interface {
	manager.Runnable
}

// NewInspector creates an Inspector.
func NewInspector(cfg *config.Config, tlsConfig *tls.Config) Inspector {
	return newInspector(cfg, tlsConfig)
}

// newInspector creates an inspector.
func newInspector(cfg *config.Config, tlsConfig *tls.Config) *inspector {
	rssInformerFactory := externalversions.NewSharedInformerFactory(cfg.RSSClient, 0)
	cmInformerFactory := utils.BuildCoordinatorInformerFactory(cfg.KubeClient)
	shuffleInformerFactory := utils.BuildShuffleServerInformerFactory(cfg.KubeClient)
	i := &inspector{
		ignoreLastApps:               cfg.IgnoreLastApps,
		ignoreRSS:                    cfg.IgnoreRSS,
		tlsConfig:                    tlsConfig,
		kubeClient:                   cfg.KubeClient,
		rssClient:                    cfg.RSSClient,
		rssInformerFactory:           rssInformerFactory,
		rssInformer:                  rssInformerFactory.Uniffle().V1alpha1().RemoteShuffleServices().Informer(),
		rssLister:                    rssInformerFactory.Uniffle().V1alpha1().RemoteShuffleServices().Lister(),
		cmInformerFactory:            cmInformerFactory,
		cmInformer:                   cmInformerFactory.Core().V1().ConfigMaps().Informer(),
		cmLister:                     cmInformerFactory.Core().V1().ConfigMaps().Lister(),
		shuffleServerInformerFactory: shuffleInformerFactory,
		hpaInformer:                  shuffleInformerFactory.Autoscaling().V2().HorizontalPodAutoscalers().Informer(),
		hpaLister:                    shuffleInformerFactory.Autoscaling().V2().HorizontalPodAutoscalers().Lister(),
		stsInformer:                  shuffleInformerFactory.Apps().V1().StatefulSets().Informer(),
		stsLister:                    shuffleInformerFactory.Apps().V1().StatefulSets().Lister(),
	}

	// register handler functions for admission webhook server.
	mux := http.NewServeMux()
	mux.HandleFunc(webhookconstants.ValidatingPodPath,
		util.WithAdmissionReviewHandler(i.validateDeletingShuffleServer))
	mux.HandleFunc(webhookconstants.ValidatingRssPath,
		util.WithAdmissionReviewHandler(i.validateRSS))
	mux.HandleFunc(webhookconstants.MutatingRssPath,
		util.WithAdmissionReviewHandler(i.mutateRSS))
	i.server = &http.Server{
		Addr:      cfg.Addr(),
		Handler:   mux,
		TLSConfig: tlsConfig,
	}

	return i
}

// inspector implements the Inspector interface.
type inspector struct {
	ignoreLastApps               bool
	ignoreRSS                    bool
	tlsConfig                    *tls.Config
	server                       *http.Server
	kubeClient                   kubernetes.Interface
	rssClient                    versioned.Interface
	rssInformerFactory           externalversions.SharedInformerFactory
	rssInformer                  cache.SharedIndexInformer
	rssLister                    v1alpha1.RemoteShuffleServiceLister
	cmInformerFactory            informers.SharedInformerFactory
	cmInformer                   cache.SharedIndexInformer
	cmLister                     corelisters.ConfigMapLister
	shuffleServerInformerFactory informers.SharedInformerFactory
	hpaInformer                  cache.SharedIndexInformer
	hpaLister                    auotscalinglisters.HorizontalPodAutoscalerLister
	stsInformer                  cache.SharedIndexInformer
	stsLister                    appslisters.StatefulSetLister
}

// Start starts the Inspector.
func (i *inspector) Start(ctx context.Context) error {
	if err := i.startInformerFactories(ctx); err != nil {
		return err
	}
	// set up the http server for listening pods and rss objects' validating and mutating requests.
	if err := i.server.ListenAndServeTLS("", ""); err != nil {
		return fmt.Errorf("listen error: %v", err)
	}
	klog.V(2).Info("inspector started")
	return nil
}

func (i *inspector) startInformerFactories(ctx context.Context) error {
	i.rssInformerFactory.Start(ctx.Done())
	i.cmInformerFactory.Start(ctx.Done())
	i.shuffleServerInformerFactory.Start(ctx.Done())
	if !cache.WaitForCacheSync(ctx.Done(), i.rssInformer.HasSynced, i.cmInformer.HasSynced,
		i.hpaInformer.HasSynced, i.stsInformer.HasSynced) {
		return fmt.Errorf("wait for cache synced failed")
	}
	return nil
}
