/*
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 knative

import (
	"context"

	"github.com/apache/camel-k/pkg/client"
	kubernetesutils "github.com/apache/camel-k/pkg/util/kubernetes"
	"github.com/apache/camel-k/pkg/util/log"
	k8serrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/client-go/dynamic"
	"k8s.io/client-go/kubernetes"
)

// IsEnabledInNamespace returns true if we can list some basic knative objects in the given namespace.
//
// This method can be used at operator level to check if knative resources can be accessed.
func IsEnabledInNamespace(ctx context.Context, c client.Client, namespace string) bool {
	dyn, err := dynamic.NewForConfig(c.GetConfig())
	if err != nil {
		log.Infof("could not create dynamic client to check knative installation in namespace %s, got error: %v", namespace, err)
		return false
	}
	for _, kgv := range RequiredKinds {
		_, err = dyn.Resource(schema.GroupVersionResource{
			Group:    kgv.Group,
			Version:  kgv.Version,
			Resource: kgv.Resource,
		}).Namespace(namespace).List(ctx, metav1.ListOptions{})

		if err == nil {
			return true
		}
	}

	log.Infof("could not find any knative type in namespace %s, last error was: %v", namespace, err)
	return false
}

// IsInstalled returns true if we are connected to a cluster with Knative installed
//
// This method should not be called from the operator, as it might require permissions that are not available.
func IsInstalled(ctx context.Context, c kubernetes.Interface) (bool, error) {
	// check some Knative APIs
	for _, api := range getRequiredKnativeGroupVersions() {
		if installed, err := isInstalled(c, api); err != nil {
			return false, err
		} else if installed {
			return true, nil
		}
	}
	return false, nil
}

func isInstalled(c kubernetes.Interface, api schema.GroupVersion) (bool, error) {
	_, err := c.Discovery().ServerResourcesForGroupVersion(api.String())
	if err != nil && (k8serrors.IsNotFound(err) || kubernetesutils.IsUnknownAPIError(err)) {
		return false, nil
	} else if err != nil {
		return false, err
	}
	return true, nil
}

func getRequiredKnativeGroupVersions() []schema.GroupVersion {
	apis := make(map[schema.GroupVersion]bool)
	res := make([]schema.GroupVersion, 0)
	for _, gvk := range RequiredKinds {
		if !apis[gvk.GroupVersion()] {
			apis[gvk.GroupVersion()] = true
			res = append(res, gvk.GroupVersion())
		}
	}
	return res
}
