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

import (
	"fmt"
	"time"

	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"

	"github.com/apache/apisix-ingress-controller/internal/types"
	"github.com/apache/apisix-ingress-controller/test/e2e/scaffold"
)

var _ = Describe("Test GatewayProxy", Label("apisix.apache.org", "v1alpha1", "gatewayproxy"), func() {
	s := scaffold.NewDefaultScaffold()

	var defaultGatewayClass = `
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
  name: %s
spec:
  controllerName: %s
`

	var gatewayWithProxy = `
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: %s
spec:
  gatewayClassName: %s
  listeners:
    - name: http
      protocol: HTTP
      port: 80
  infrastructure:
    parametersRef:
      group: apisix.apache.org
      kind: GatewayProxy
      name: %s
`

	var gatewayProxyWithEnabledPlugin = `
apiVersion: apisix.apache.org/v1alpha1
kind: GatewayProxy
metadata:
  name: %s
spec:
  provider:
    type: ControlPlane
    controlPlane:
      endpoints:
        - %s
      auth:
        type: AdminKey
        adminKey:
          value: "%s"
  plugins:
  - name: response-rewrite
    enabled: true
    config: 
      headers:
        X-Proxy-Test: "enabled"
`

	var gatewayProxyWithDisabledPlugin = `
apiVersion: apisix.apache.org/v1alpha1
kind: GatewayProxy
metadata:
  name: %s
spec:
  provider:
    type: ControlPlane
    controlPlane:
      endpoints:
        - %s
      auth:
        type: AdminKey
        adminKey:
          value: "%s"
  plugins:
  - name: response-rewrite
    enabled: false
    config: 
      headers:
        X-Proxy-Test: "disabled"
`

	var httpRouteForTest = `
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: test-route
spec:
  parentRefs:
  - name: %s
  hostnames:
  - example.com
  rules:
  - matches:
    - path:
        type: Exact
        value: /get
    backendRefs:
    - name: httpbin-service-e2e-test
      port: 80
`

	var (
		gatewayClassName string
	)

	BeforeEach(func() {
		By("Create GatewayClass")
		gatewayClassName = s.Namespace()
		err := s.CreateResourceFromStringWithNamespace(fmt.Sprintf(defaultGatewayClass, gatewayClassName, s.GetControllerName()), "")
		Expect(err).NotTo(HaveOccurred(), "creating GatewayClass")
		time.Sleep(5 * time.Second)

		By("Check GatewayClass condition")
		gcYaml, err := s.GetResourceYaml(types.KindGatewayClass, gatewayClassName)
		Expect(err).NotTo(HaveOccurred(), "getting GatewayClass yaml")
		Expect(gcYaml).To(ContainSubstring(`status: "True"`), "checking GatewayClass condition status")
		Expect(gcYaml).To(ContainSubstring("message: the gatewayclass has been accepted by the apisix-ingress-controller"), "checking GatewayClass condition message")

		By("Create GatewayProxy with enabled plugin")
		err = s.CreateResourceFromString(fmt.Sprintf(gatewayProxyWithEnabledPlugin, s.Namespace(), s.Deployer.GetAdminEndpoint(), s.AdminKey()))
		Expect(err).NotTo(HaveOccurred(), "creating GatewayProxy with enabled plugin")
		time.Sleep(5 * time.Second)

		By("Create Gateway with GatewayProxy")
		err = s.CreateResourceFromStringWithNamespace(fmt.Sprintf(gatewayWithProxy, s.Namespace(), gatewayClassName, s.Namespace()), s.Namespace())
		Expect(err).NotTo(HaveOccurred(), "creating Gateway with GatewayProxy")
		time.Sleep(5 * time.Second)

		By("check Gateway condition")
		gwyaml, err := s.GetResourceYaml(types.KindGateway, s.Namespace())
		Expect(err).NotTo(HaveOccurred(), "getting Gateway yaml")
		Expect(gwyaml).To(ContainSubstring(`status: "True"`), "checking Gateway condition status")
		Expect(gwyaml).To(ContainSubstring("message: the gateway has been accepted by the apisix-ingress-controller"), "checking Gateway condition message")
	})

	Context("Test Gateway with enabled GatewayProxy plugin", func() {
		It("Should apply plugin configuration when enabled", func() {
			By("Create HTTPRoute for Gateway with GatewayProxy")
			s.ResourceApplied(types.KindHTTPRoute, "test-route", fmt.Sprintf(httpRouteForTest, s.Namespace()), 1)

			By("Check if the plugin is applied")
			s.RequestAssert(&scaffold.RequestAssert{
				Method: "GET",
				Path:   "/get",
				Host:   "example.com",
				Checks: []scaffold.ResponseCheckFunc{
					scaffold.WithExpectedStatus(200),
					scaffold.WithExpectedHeader("X-Proxy-Test", "enabled"),
				},
			})

			By("Update GatewayProxy with disabled plugin")
			err := s.CreateResourceFromString(fmt.Sprintf(gatewayProxyWithDisabledPlugin, s.Namespace(), s.Deployer.GetAdminEndpoint(), s.AdminKey()))
			Expect(err).NotTo(HaveOccurred(), "updating GatewayProxy with disabled plugin")

			By("Create HTTPRoute for Gateway with GatewayProxy")
			s.ResourceApplied(types.KindHTTPRoute, "test-route", fmt.Sprintf(httpRouteForTest, s.Namespace()), 1)

			By("Check if the plugin is not applied")
			s.RequestAssert(&scaffold.RequestAssert{
				Method: "GET",
				Path:   "/get",
				Host:   "example.com",
				Checks: []scaffold.ResponseCheckFunc{
					scaffold.WithExpectedStatus(200),
					scaffold.WithExpectedHeader("X-Proxy-Test", ""),
				},
			})

			By("should fail to apply GatewayProxy with invalid endpoint")
			var gatewayProxyWithInvalidEndpoint = `
apiVersion: apisix.apache.org/v1alpha1
kind: GatewayProxy
metadata:
  name: %s
spec:
  provider:
    type: ControlPlane
    controlPlane:
      endpoints:
        - "http://invalid-endpoint:9180"
        - %s
      auth:
        type: AdminKey
        adminKey:
          value: "%s"
`

			By("Update GatewayProxy with invalid endpoint")
			err = s.CreateResourceFromString(fmt.Sprintf(gatewayProxyWithInvalidEndpoint, s.Namespace(), s.Deployer.GetAdminEndpoint(), s.AdminKey()))
			Expect(err).NotTo(HaveOccurred(), "creating GatewayProxy with enabled plugin")

			By("Create HTTPRoute")
			s.ResourceApplied(types.KindHTTPRoute, "test-route", fmt.Sprintf(httpRouteForTest, s.Namespace()), 1)

			s.RequestAssert(&scaffold.RequestAssert{
				Method: "GET",
				Path:   "/get",
				Host:   "example.com",
				Checks: []scaffold.ResponseCheckFunc{
					scaffold.WithExpectedStatus(200),
					scaffold.WithExpectedHeader("X-Proxy-Test", ""),
				},
			})
		})
	})

	Context("Test GatewayProxy Provider Validation", func() {
		var (
			gatewayProxyWithInvalidProviderType = `
apiVersion: apisix.apache.org/v1alpha1
kind: GatewayProxy
metadata:
  name: %s
spec:
  provider:
    type: "InvalidType"
`
			gatewayProxyWithMissingControlPlane = `
apiVersion: apisix.apache.org/v1alpha1
kind: GatewayProxy
metadata:
  name: %s
spec:
  provider:
    type: "ControlPlane"
`
			gatewayProxyWithValidProvider = `
apiVersion: apisix.apache.org/v1alpha1
kind: GatewayProxy
metadata:
  name: %s
spec:
  provider:
    type: "ControlPlane"
    controlPlane:
      endpoints:
        - "http://localhost:9180"
      auth:
        type: "AdminKey"
        adminKey:
          value: "test-key"
`
		)
		It("Should reject invalid provider type", func() {
			By("Create GatewayProxy with invalid provider type")
			err := s.CreateResourceFromString(fmt.Sprintf(gatewayProxyWithInvalidProviderType, s.Namespace()))
			Expect(err).To(HaveOccurred(), "creating GatewayProxy with invalid provider type")
			Expect(err.Error()).To(ContainSubstring("Invalid value"))
		})

		It("Should reject missing controlPlane configuration", func() {
			By("Create GatewayProxy with missing controlPlane")
			err := s.CreateResourceFromString(fmt.Sprintf(gatewayProxyWithMissingControlPlane, s.Namespace()))
			Expect(err).To(HaveOccurred(), "creating GatewayProxy with missing controlPlane")
			Expect(err.Error()).To(ContainSubstring("controlPlane must be specified when type is ControlPlane"))
		})

		It("Should accept valid provider configuration", func() {
			By("Create GatewayProxy with valid provider")
			err := s.CreateResourceFromString(fmt.Sprintf(gatewayProxyWithValidProvider, s.Namespace()))
			Expect(err).NotTo(HaveOccurred(), "creating GatewayProxy with valid provider")

			s.RetryAssertion(func() string {
				gpYaml, _ := s.GetResourceYaml(types.KindGatewayProxy, s.Namespace())
				return gpYaml
			}).Should(ContainSubstring(`"type":"ControlPlane"`), "checking GatewayProxy is applied")
		})
	})
})
