// 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 types

import (
	corev1 "k8s.io/api/core/v1"
	netv1 "k8s.io/api/networking/v1"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/utils/ptr"
	gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"

	"github.com/apache/apisix-ingress-controller/api/v1alpha1"
	v2 "github.com/apache/apisix-ingress-controller/api/v2"
)

const (
	DefaultIngressClassAnnotation = "ingressclass.kubernetes.io/is-default-class"
	IngressClassNameAnnotation    = "kubernetes.io/ingress.class"
)

const (
	KindGateway              = "Gateway"
	KindHTTPRoute            = "HTTPRoute"
	KindTCPRoute             = "TCPRoute"
	KindUDPRoute             = "UDPRoute"
	KindGRPCRoute            = "GRPCRoute"
	KindTLSRoute             = "TLSRoute"
	KindGatewayClass         = "GatewayClass"
	KindReferenceGrant       = "ReferenceGrant"
	KindIngress              = "Ingress"
	KindIngressClass         = "IngressClass"
	KindGatewayProxy         = "GatewayProxy"
	KindSecret               = "Secret"
	KindConfigMap            = "ConfigMap"
	KindService              = "Service"
	KindApisixRoute          = "ApisixRoute"
	KindApisixGlobalRule     = "ApisixGlobalRule"
	KindApisixPluginConfig   = "ApisixPluginConfig"
	KindPod                  = "Pod"
	KindApisixTls            = "ApisixTls"
	KindApisixConsumer       = "ApisixConsumer"
	KindHTTPRoutePolicy      = "HTTPRoutePolicy"
	KindL4RoutePolicy        = "L4RoutePolicy"
	KindBackendTrafficPolicy = "BackendTrafficPolicy"
	KindConsumer             = "Consumer"
	KindPluginConfig         = "PluginConfig"
	KindApisixUpstream       = "ApisixUpstream"
)

const (
	AppProtocolHTTP  = "http"
	AppProtocolHTTPS = "https"
	AppProtocolWS    = "kubernetes.io/ws"
	AppProtocolWSS   = "kubernetes.io/wss"
)

func KindOf(obj any) string {
	switch obj.(type) {
	case *gatewayv1.Gateway:
		return KindGateway
	case *gatewayv1.TCPRoute:
		return KindTCPRoute
	case *gatewayv1.UDPRoute:
		return KindUDPRoute
	case *gatewayv1.HTTPRoute:
		return KindHTTPRoute
	case *gatewayv1.GRPCRoute:
		return KindGRPCRoute
	case *gatewayv1.TLSRoute:
		return KindTLSRoute
	case *gatewayv1.GatewayClass:
		return KindGatewayClass
	case *gatewayv1.ReferenceGrant:
		return KindReferenceGrant
	case *netv1.Ingress:
		return KindIngress
	case *netv1.IngressClass:
		return KindIngressClass
	case *corev1.Secret:
		return KindSecret
	case *corev1.Service:
		return KindService
	case *v2.ApisixRoute:
		return KindApisixRoute
	case *v2.ApisixGlobalRule:
		return KindApisixGlobalRule
	case *v2.ApisixPluginConfig:
		return KindApisixPluginConfig
	case *v2.ApisixUpstream:
		return KindApisixUpstream
	case *corev1.Pod:
		return KindPod
	case *v2.ApisixTls:
		return KindApisixTls
	case *v2.ApisixConsumer:
		return KindApisixConsumer
	case *v1alpha1.HTTPRoutePolicy:
		return KindHTTPRoutePolicy
	case *v1alpha1.L4RoutePolicy:
		return KindL4RoutePolicy
	case *v1alpha1.BackendTrafficPolicy:
		return KindBackendTrafficPolicy
	case *v1alpha1.GatewayProxy:
		return KindGatewayProxy
	case *v1alpha1.Consumer:
		return KindConsumer
	case *v1alpha1.PluginConfig:
		return KindPluginConfig
	default:
		return "Unknown"
	}
}

func GvkOf(obj any) schema.GroupVersionKind {
	kind := KindOf(obj)
	switch obj.(type) {
	case *gatewayv1.Gateway, *gatewayv1.HTTPRoute, *gatewayv1.GatewayClass, *gatewayv1.GRPCRoute,
		*gatewayv1.TCPRoute, *gatewayv1.UDPRoute, *gatewayv1.TLSRoute:
		return schema.GroupVersion(gatewayv1.GroupVersion).WithKind(kind)
	case *gatewayv1.ReferenceGrant:
		return schema.GroupVersion(gatewayv1.GroupVersion).WithKind(kind)
	case *netv1.Ingress, *netv1.IngressClass:
		return netv1.SchemeGroupVersion.WithKind(kind)
	case *corev1.Secret, *corev1.Service:
		return corev1.SchemeGroupVersion.WithKind(kind)
	case *v2.ApisixRoute:
		return schema.GroupVersionKind{
			Group:   "apisix.apache.org",
			Version: "v2",
			Kind:    KindApisixRoute,
		}
	case *v2.ApisixGlobalRule:
		return schema.GroupVersionKind{
			Group:   "apisix.apache.org",
			Version: "v2",
			Kind:    KindApisixGlobalRule,
		}
	case *v2.ApisixPluginConfig:
		return schema.GroupVersionKind{
			Group:   "apisix.apache.org",
			Version: "v2",
			Kind:    KindApisixPluginConfig,
		}
	case *v2.ApisixTls:
		return schema.GroupVersionKind{
			Group:   "apisix.apache.org",
			Version: "v2",
			Kind:    KindApisixTls,
		}
	case *v2.ApisixConsumer:
		return schema.GroupVersionKind{
			Group:   "apisix.apache.org",
			Version: "v2",
			Kind:    KindApisixConsumer,
		}
	case *v2.ApisixUpstream:
		return schema.GroupVersionKind{
			Group:   "apisix.apache.org",
			Version: "v2",
			Kind:    KindApisixUpstream,
		}
	case *v1alpha1.HTTPRoutePolicy:
		return schema.GroupVersionKind{
			Group:   "apisix.apache.org",
			Version: "v1alpha1",
			Kind:    KindHTTPRoutePolicy,
		}
	case *v1alpha1.L4RoutePolicy:
		return schema.GroupVersionKind{
			Group:   "apisix.apache.org",
			Version: "v1alpha1",
			Kind:    KindL4RoutePolicy,
		}
	case *v1alpha1.BackendTrafficPolicy:
		return schema.GroupVersionKind{
			Group:   "apisix.apache.org",
			Version: "v1alpha1",
			Kind:    KindBackendTrafficPolicy,
		}
	case *v1alpha1.GatewayProxy:
		return schema.GroupVersionKind{
			Group:   "apisix.apache.org",
			Version: "v1alpha1",
			Kind:    KindGatewayProxy,
		}
	case *v1alpha1.Consumer:
		return schema.GroupVersionKind{
			Group:   "apisix.apache.org",
			Version: "v1alpha1",
			Kind:    KindConsumer,
		}
	case *v1alpha1.PluginConfig:
		return schema.GroupVersionKind{
			Group:   "apisix.apache.org",
			Version: "v1alpha1",
			Kind:    KindPluginConfig,
		}
	default:
		return schema.GroupVersionKind{}
	}
}

// GetEffectiveIngressClassName returns the effective ingress class name.
// It first checks spec.IngressClassName, and falls back to the annotation if spec is empty.
func GetEffectiveIngressClassName(ingress *netv1.Ingress) string {
	if cls := ptr.Deref(ingress.Spec.IngressClassName, ""); cls != "" {
		return cls
	}
	return ingress.GetAnnotations()[IngressClassNameAnnotation]
}

// FrontendTLSValidationForListener resolves the Gateway-level frontend TLS
// client-certificate validation that applies to the given listener.
//
// In Gateway API v1.6 frontendValidation moved from the per-listener TLS config
// to spec.tls.frontend, and it applies only to Listeners handling HTTPS traffic:
// Default applies to all HTTPS listeners, and a PerPort entry overrides it for
// listeners on the matching port. It returns nil for any non-HTTPS listener or
// when no frontend config is set.
//
// This is the single source of truth for that resolution; the gateway
// translator, the gateway controller and the indexer all rely on it agreeing,
// otherwise a listener's status and its programmed config would silently diverge.
func FrontendTLSValidationForListener(gateway *gatewayv1.Gateway, listener gatewayv1.Listener) *gatewayv1.FrontendTLSValidation {
	if listener.Protocol != gatewayv1.HTTPSProtocolType {
		return nil
	}
	if gateway.Spec.TLS == nil || gateway.Spec.TLS.Frontend == nil {
		return nil
	}
	frontend := gateway.Spec.TLS.Frontend
	for i := range frontend.PerPort {
		if frontend.PerPort[i].Port == listener.Port {
			return frontend.PerPort[i].TLS.Validation
		}
	}
	return frontend.Default.Validation
}
