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

import (
	"fmt"
	"net"
	"strconv"

	"github.com/pkg/errors"
	corev1 "k8s.io/api/core/v1"
	discoveryv1 "k8s.io/api/discovery/v1"
	k8stypes "k8s.io/apimachinery/pkg/types"
	"k8s.io/utils/ptr"
	gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"

	types "github.com/apache/apisix-ingress-controller/api/adc"
	"github.com/apache/apisix-ingress-controller/api/v1alpha1"
	"github.com/apache/apisix-ingress-controller/internal/controller/config"
	"github.com/apache/apisix-ingress-controller/internal/provider"
	"github.com/apache/apisix-ingress-controller/internal/utils"
)

func (t *Translator) TranslateGatewayProxyToConfig(tctx *provider.TranslateContext, gatewayProxy *v1alpha1.GatewayProxy, resolveEndpoints bool) (*types.Config, error) {
	if gatewayProxy == nil || gatewayProxy.Spec.Provider == nil {
		return nil, nil
	}

	provider := gatewayProxy.Spec.Provider
	if provider.Type != v1alpha1.ProviderTypeControlPlane || provider.ControlPlane == nil {
		return nil, nil
	}
	cp := provider.ControlPlane

	cfg := types.Config{
		Name:        utils.NamespacedNameKind(gatewayProxy).String(),
		BackendType: cp.Mode,
	}

	if cp.TlsVerify != nil {
		cfg.TlsVerify = *cp.TlsVerify
	}

	if cp.Auth.Type == v1alpha1.AuthTypeAdminKey && cp.Auth.AdminKey != nil {
		if cp.Auth.AdminKey.ValueFrom != nil && cp.Auth.AdminKey.ValueFrom.SecretKeyRef != nil {
			secretRef := cp.Auth.AdminKey.ValueFrom.SecretKeyRef
			secret, ok := tctx.Secrets[k8stypes.NamespacedName{
				// we should use gateway proxy namespace
				Namespace: gatewayProxy.GetNamespace(),
				Name:      secretRef.Name,
			}]
			if ok {
				if token, ok := secret.Data[secretRef.Key]; ok {
					cfg.Token = string(token)
				}
			}
		} else if cp.Auth.AdminKey.Value != "" {
			cfg.Token = cp.Auth.AdminKey.Value
		}
	}

	if cfg.Token == "" {
		return nil, errors.New("no token found")
	}

	endpoints := cp.Endpoints
	if len(endpoints) > 0 {
		cfg.ServerAddrs = endpoints
		return &cfg, nil
	}

	// If Mode is empty, use the default static configuration.
	// If Mode is set, resolve endpoints only when the ControlPlane is in standalone mode.
	if cp.Mode != "" {
		resolveEndpoints = cp.Mode == string(config.ProviderTypeStandalone)
	}

	if cp.Service != nil {
		namespacedName := k8stypes.NamespacedName{
			Namespace: gatewayProxy.Namespace,
			Name:      cp.Service.Name,
		}
		svc, ok := tctx.Services[namespacedName]
		if !ok {
			return nil, fmt.Errorf("no service found for service reference: %s", namespacedName)
		}

		// APISIXStandalone, configurations need to be sent to each data plane instance;
		// In other cases, the service is directly accessed as the adc backend server address.
		if resolveEndpoints {
			endpoint := tctx.EndpointSlices[namespacedName]
			if endpoint == nil {
				return nil, nil
			}
			upstreamNodes, _, err := t.TranslateBackendRefWithFilter(tctx, gatewayv1.BackendRef{
				BackendObjectReference: gatewayv1.BackendObjectReference{
					Name:      gatewayv1.ObjectName(cp.Service.Name),
					Namespace: (*gatewayv1.Namespace)(&gatewayProxy.Namespace),
					Port:      ptr.To(cp.Service.Port),
				},
			}, func(endpoint *discoveryv1.Endpoint) bool {
				if endpoint.Conditions.Terminating != nil && *endpoint.Conditions.Terminating {
					t.Log.V(1).Info("skip terminating endpoint", "endpoint", endpoint)
					return false
				}
				return true
			})
			if err != nil {
				return nil, err
			}
			for _, node := range upstreamNodes {
				cfg.ServerAddrs = append(cfg.ServerAddrs, "http://"+net.JoinHostPort(node.Host, strconv.Itoa(node.Port)))
			}
		} else {
			refPort := cp.Service.Port
			var serverAddr string
			if svc.Spec.Type == corev1.ServiceTypeExternalName {
				serverAddr = fmt.Sprintf("http://%s:%d", svc.Spec.ExternalName, refPort)
			} else {
				serverAddr = fmt.Sprintf("http://%s.%s.svc:%d", cp.Service.Name, gatewayProxy.Namespace, refPort)
			}
			cfg.ServerAddrs = []string{serverAddr}
		}

		t.Log.V(1).Info("add server address to config.ServiceAddrs", "config.ServerAddrs", cfg.ServerAddrs)
	}

	return &cfg, nil
}
