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

import (
	"fmt"
	"reflect"
	"strings"
	"time"

	"github.com/getkin/kin-openapi/openapi3"
	"github.com/pkg/errors"

	"github.com/apisix/manager-api/internal/core/entity"
	"github.com/apisix/manager-api/internal/handler/data_loader/loader"
	"github.com/apisix/manager-api/internal/utils/consts"
)

func (o Loader) Import(input interface{}) (*loader.DataSets, error) {
	if input == nil {
		panic("input is nil")
	}

	d, ok := input.([]byte)
	if !ok {
		panic(fmt.Sprintf("input format error: expected []byte but it is %s", reflect.TypeOf(input).Kind().String()))
	}

	// load OAS3 document
	swagger, err := openapi3.NewSwaggerLoader().LoadSwaggerFromData(d)
	if err != nil {
		return nil, err
	}

	// no paths in OAS3 document
	if len(swagger.Paths) <= 0 {
		return nil, errors.Wrap(errors.New("OpenAPI documentation does not contain any paths"), consts.ErrImportFile.Error())
	}

	if o.TaskName == "" {
		o.TaskName = "openapi_" + time.Now().Format("20060102150405")
	}

	data, err := o.convertToEntities(swagger)
	if err != nil {
		return nil, err
	}

	return data, nil
}

func (o Loader) convertToEntities(s *openapi3.Swagger) (*loader.DataSets, error) {
	var (
		// temporarily save the parsed data
		data = &loader.DataSets{}
		// global upstream ID
		globalUpstreamID = o.TaskName
		// global uri prefix
		globalPath = ""
	)

	// create upstream when servers field not empty
	if len(s.Servers) > 0 {
		upstream := entity.Upstream{
			BaseInfo: entity.BaseInfo{ID: globalUpstreamID},
			UpstreamDef: entity.UpstreamDef{
				Name: globalUpstreamID,
				Type: "roundrobin",
				Nodes: map[string]float64{
					"0.0.0.0": 1,
				},
			},
		}
		data.Upstreams = append(data.Upstreams, upstream)
	}

	// each one will correspond to a route
	for uri, v := range s.Paths {
		// replace parameter in uri to wildcard
		realUri := regURIVar.ReplaceAllString(uri, "*")
		// generate route Name
		routeName := o.TaskName + "_" + strings.TrimPrefix(uri, "/")

		// decide whether to merge multi-method routes based on configuration
		if o.MergeMethod {
			// create a single route for each path, merge all methods
			route := generateBaseRoute(routeName, v.Summary)
			route.Uris = []string{globalPath + realUri}
			route.UpstreamID = globalUpstreamID
			for method := range v.Operations() {
				route.Methods = append(route.Methods, strings.ToUpper(method))
			}
			data.Routes = append(data.Routes, route)
		} else {
			// create routes for each method of each path
			for method, operation := range v.Operations() {
				subRouteID := routeName + "_" + method
				route := generateBaseRoute(subRouteID, operation.Summary)
				route.Uris = []string{globalPath + realUri}
				route.Methods = []string{strings.ToUpper(method)}
				route.UpstreamID = globalUpstreamID
				data.Routes = append(data.Routes, route)
			}
		}
	}
	return data, nil
}

// Generate a base route for customize
func generateBaseRoute(name string, desc string) entity.Route {
	return entity.Route{
		Name:    name,
		Desc:    desc,
		Plugins: make(map[string]interface{}),
	}
}
