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

import (
	"encoding/json"
	"net/http"
	"path/filepath"

	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
	"github.com/savsgio/gotils/bytes"
	"github.com/tidwall/gjson"

	"github.com/apisix/manager-api/test/e2e/base"
)

var _ = Describe("OpenAPI 3", func() {
	DescribeTable("Import cases",
		func(f func()) {
			f()
		},
		Entry("default.yaml", func() {
			path, err := filepath.Abs("../../testdata/import/default.yaml")
			Expect(err).To(BeNil())

			req := base.ManagerApiExpect().POST("/apisix/admin/import/routes")
			req.WithMultipart().WithForm(map[string]string{
				"type":      "openapi3",
				"task_name": "test_default_yaml",
				"_file":     "default.yaml",
			})
			req.WithMultipart().WithFile("file", path)
			req.WithHeader("Authorization", base.GetToken())
			resp := req.Expect()
			resp.Status(http.StatusOK)
			r := gjson.ParseBytes([]byte(resp.Body().Raw()))

			Expect(r.Get("code").Uint()).To(Equal(uint64(0)))

			r = r.Get("data")
			for s, result := range r.Map() {
				if s == "route" {
					Expect(result.Get("total").Uint()).To(Equal(uint64(1)))
					Expect(result.Get("failed").Uint()).To(Equal(uint64(0)))
				}
			}
		}),
		Entry("default.json", func() {
			path, err := filepath.Abs("../../testdata/import/default.json")
			Expect(err).To(BeNil())

			req := base.ManagerApiExpect().POST("/apisix/admin/import/routes")
			req.WithMultipart().WithForm(map[string]string{
				"type":      "openapi3",
				"task_name": "test_default_json",
				"_file":     "default.json",
			})
			req.WithMultipart().WithFile("file", path)
			req.WithHeader("Authorization", base.GetToken())
			resp := req.Expect()
			resp.Status(http.StatusOK)
			r := gjson.ParseBytes([]byte(resp.Body().Raw()))

			Expect(r.Get("code").Uint()).To(Equal(uint64(0)))

			r = r.Get("data")
			for s, result := range r.Map() {
				if s == "route" {
					Expect(result.Get("total").Uint()).To(Equal(uint64(1)))
					Expect(result.Get("failed").Uint()).To(Equal(uint64(0)))
				}
			}
		}),
		Entry("Postman-API101.yaml merge method", func() {
			path, err := filepath.Abs("../../testdata/import/Postman-API101.yaml")
			Expect(err).To(BeNil())

			req := base.ManagerApiExpect().POST("/apisix/admin/import/routes")
			req.WithMultipart().WithForm(map[string]string{
				"type":         "openapi3",
				"task_name":    "test_postman_api101_yaml_mm",
				"_file":        "Postman-API101.yaml",
				"merge_method": "true",
			})
			req.WithMultipart().WithFile("file", path)
			req.WithHeader("Authorization", base.GetToken())
			resp := req.Expect()
			resp.Status(http.StatusOK)
			r := gjson.ParseBytes([]byte(resp.Body().Raw()))

			Expect(r.Get("code").Uint()).To(Equal(uint64(0)))

			r = r.Get("data")
			for s, result := range r.Map() {
				if s == "route" {
					Expect(result.Get("total").Uint()).To(Equal(uint64(3)))
					Expect(result.Get("failed").Uint()).To(Equal(uint64(0)))
				}
				if s == "upstream" {
					Expect(result.Get("total").Uint()).To(Equal(uint64(1)))
					Expect(result.Get("failed").Uint()).To(Equal(uint64(0)))
				}
			}
		}),
		Entry("Postman-API101.yaml non-merge method", func() {
			// clean routes
			base.CleanResource("routes")
			path, err := filepath.Abs("../../testdata/import/Postman-API101.yaml")
			Expect(err).To(BeNil())

			req := base.ManagerApiExpect().POST("/apisix/admin/import/routes")
			req.WithMultipart().WithForm(map[string]string{
				"type":         "openapi3",
				"task_name":    "test_postman_api101_yaml_nmm",
				"_file":        "Postman-API101.yaml",
				"merge_method": "false",
			})
			req.WithMultipart().WithFile("file", path)
			req.WithHeader("Authorization", base.GetToken())
			resp := req.Expect()
			resp.Status(http.StatusOK)
			r := gjson.ParseBytes([]byte(resp.Body().Raw()))

			Expect(r.Get("code").Uint()).To(Equal(uint64(0)))

			r = r.Get("data")
			for s, result := range r.Map() {
				if s == "route" {
					Expect(result.Get("total").Uint()).To(Equal(uint64(5)))
					Expect(result.Get("failed").Uint()).To(Equal(uint64(0)))
				}
				if s == "upstream" {
					Expect(result.Get("total").Uint()).To(Equal(uint64(1)))
					Expect(result.Get("failed").Uint()).To(Equal(uint64(0)))
				}
			}
		}),
		Entry("Clean resources", func() {
			base.CleanResource("routes")
			base.CleanResource("upstreams")
			base.CleanResource("services")
		}),
	)
	DescribeTable("Exception cases",
		func(f func()) {
			f()
		},
		Entry("Empty upload file", func() {
			req := base.ManagerApiExpect().POST("/apisix/admin/import/routes")
			req.WithMultipart().WithForm(map[string]string{
				"type":      "openapi3",
				"task_name": "empty_upload",
				"_file":     "default.yaml",
			})
			req.WithHeader("Authorization", base.GetToken())
			resp := req.Expect()
			resp.Status(http.StatusOK)
			r := gjson.ParseBytes([]byte(resp.Body().Raw()))

			Expect(r.Get("code").Uint()).To(Equal(uint64(10000)))
			Expect(r.Get("message").String()).To(Equal("uploaded file is empty"))
		}),
		Entry("Exceed limit upload file", func() {
			req := base.ManagerApiExpect().POST("/apisix/admin/import/routes")
			req.WithMultipart().WithForm(map[string]string{
				"type":      "openapi3",
				"task_name": "exceed_limit_upload",
				"_file":     "default.yaml",
			})

			req.WithMultipart().WithFileBytes("file", "default.yaml", bytes.Rand(make([]byte, 10*1024*1024+1)))
			req.WithHeader("Authorization", base.GetToken())
			resp := req.Expect()
			resp.Status(http.StatusOK)
			r := gjson.ParseBytes([]byte(resp.Body().Raw()))

			Expect(r.Get("code").Uint()).To(Equal(uint64(10000)))
			Expect(r.Get("message").String()).To(Equal("uploaded file size exceeds the limit, limit is 10485760"))
		}),
		Entry("Routes duplicate #1", func() {
			path, err := filepath.Abs("../../testdata/import/Postman-API101.yaml")
			Expect(err).To(BeNil())
			req := base.ManagerApiExpect().POST("/apisix/admin/import/routes")
			req.WithMultipart().WithForm(map[string]string{
				"type":         "openapi3",
				"task_name":    "duplicate",
				"_file":        "Postman-API101.yaml",
				"merge_method": "true",
			})
			req.WithMultipart().WithFile("file", path)
			req.WithHeader("Authorization", base.GetToken())
			resp := req.Expect()
			resp.Status(http.StatusOK)
			r := gjson.ParseBytes([]byte(resp.Body().Raw()))
			Expect(r.Get("code").Uint()).To(Equal(uint64(0)))
		}),
		Entry("Route duplicate #2", func() {
			path, err := filepath.Abs("../../testdata/import/Postman-API101.yaml")
			Expect(err).To(BeNil())
			req := base.ManagerApiExpect().POST("/apisix/admin/import/routes")
			req.WithMultipart().WithForm(map[string]string{
				"type":         "openapi3",
				"task_name":    "duplicate",
				"_file":        "Postman-API101.yaml",
				"merge_method": "true",
			})
			req.WithMultipart().WithFile("file", path)
			req.WithHeader("Authorization", base.GetToken())
			resp := req.Expect()
			resp.Status(http.StatusOK)
			r := gjson.ParseBytes([]byte(resp.Body().Raw()))
			Expect(r.Get("code").Uint()).To(Equal(uint64(0)))
			Expect(r.Get("data").Map()["route"].Get("failed").Uint()).To(Equal(uint64(1)))
			Expect(r.Get("data").Map()["route"].Get("errors").Array()[0].String()).
				To(ContainSubstring("is duplicated with route duplicate_"))

		}),
		Entry("Clean resources", func() {
			base.CleanResource("routes")
			base.CleanResource("upstreams")
			base.CleanResource("services")
		}),
	)
	DescribeTable("Real API cases",
		func(f func()) {
			f()
		},
		Entry("Import httpbin.org YAML", func() {
			path, err := filepath.Abs("../../testdata/import/httpbin.yaml")
			Expect(err).To(BeNil())

			req := base.ManagerApiExpect().POST("/apisix/admin/import/routes")
			req.WithMultipart().WithForm(map[string]string{
				"type":      "openapi3",
				"task_name": "httpbin",
				"_file":     "httpbin.yaml",
			})
			req.WithMultipart().WithFile("file", path)
			req.WithHeader("Authorization", base.GetToken())
			resp := req.Expect()
			resp.Status(http.StatusOK)
			r := gjson.ParseBytes([]byte(resp.Body().Raw()))

			Expect(r.Get("code").Uint()).To(Equal(uint64(0)))

			r = r.Get("data")
			for s, result := range r.Map() {
				if s == "route" {
					Expect(result.Get("total").Uint()).To(Equal(uint64(1)))
					Expect(result.Get("failed").Uint()).To(Equal(uint64(0)))
				}
			}
		}),
		Entry("Modify upstream", func() {
			body := make(map[string]interface{})
			body["nodes"] = []map[string]interface{}{
				{
					"host":   "httpbin.org",
					"port":   80,
					"weight": 1,
				},
			}
			body["type"] = "roundrobin"
			_body, err := json.Marshal(body)
			Expect(err).To(BeNil())
			base.RunTestCase(base.HttpTestCase{
				Object:       base.ManagerApiExpect(),
				Method:       http.MethodPatch,
				Path:         "/apisix/admin/upstreams/httpbin",
				Body:         string(_body),
				Headers:      map[string]string{"Authorization": base.GetToken()},
				ExpectStatus: http.StatusOK,
			})
		}),
		Entry("Enable route", func() {
			// get route id
			req := base.ManagerApiExpect().GET("/apisix/admin/routes")
			req.WithHeader("Authorization", base.GetToken())
			resp := req.Expect()
			resp.Status(http.StatusOK)
			r := gjson.ParseBytes([]byte(resp.Body().Raw()))
			Expect(r.Get("code").Uint()).To(Equal(uint64(0)))
			id := r.Get("data").Get("rows").Array()[0].Get("id").String()

			body := make(map[string]interface{})
			body["status"] = 1
			_body, err := json.Marshal(body)
			Expect(err).To(BeNil())
			base.RunTestCase(base.HttpTestCase{
				Object:       base.ManagerApiExpect(),
				Method:       http.MethodPatch,
				Path:         "/apisix/admin/routes/" + id,
				Body:         string(_body),
				Headers:      map[string]string{"Authorization": base.GetToken()},
				ExpectStatus: http.StatusOK,
				Sleep:        base.SleepTime,
			})
		}),
		Entry("Request API", func() {
			req := base.APISIXExpect().GET("/get")
			resp := req.Expect()
			resp.Status(http.StatusOK)
			r := gjson.ParseBytes([]byte(resp.Body().Raw()))
			Expect(r.Get("headers").Get("User-Agent").String()).To(Equal("Go-http-client/1.1"))
		}),
	)
})
