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

import (
	"net/url"
	"strings"
	"sync"
)

import (
	"github.com/dubbo-go-pixiu/pixiu-api/pkg/api/config"
	"github.com/dubbo-go-pixiu/pixiu-api/pkg/router"

	"github.com/pkg/errors"
)

import (
	"github.com/apache/dubbo-go-pixiu/pkg/common/constant"
	"github.com/apache/dubbo-go-pixiu/pkg/common/router/trie"
	"github.com/apache/dubbo-go-pixiu/pkg/common/util/stringutil"
	"github.com/apache/dubbo-go-pixiu/pkg/logger"
)

// Node defines the single method of the router configured API
type Node struct {
	fullPath string
	filters  []string // nolint:unused
	method   *config.Method
	headers  map[string]string
}

// Route defines the tree of router APIs
type Route struct {
	lock sync.RWMutex
	tree trie.Trie
}

// ClearAPI clear the api
func (rt *Route) ClearAPI() error {
	rt.lock.Lock()
	defer rt.lock.Unlock()
	rt.tree.Clear()
	return nil
}

func (rt *Route) RemoveAPI(api router.API) {
	lowerCasePath := strings.ToLower(api.URLPattern)
	key := getTrieKey(api.HTTPVerb, lowerCasePath, false)

	rt.lock.Lock()
	defer rt.lock.Unlock()
	// if api is exists
	if exists, err := rt.tree.Contains(key); err != nil {
		logger.Errorf("rt.tree.Get(key) err: %s", err.Error())
		return
	} else if exists {
		// append new cluster to the node
		if node, _, exists, err := rt.tree.Get(key); err != nil {
			logger.Errorf("rt.tree.Get(key) err: %s", err.Error())
			return
		} else if exists {
			bizInfoInterface := node.GetBizInfo()
			bizInfo, ok := bizInfoInterface.(*Node)
			if bizInfo == nil || !ok {
				logger.Error("bizInfoInterface.(*Node) failed")
				return
			}
			// avoid thread safe problem
			clusters := strings.Split(bizInfo.method.URL, ",")
			if len(clusters) > 1 {
				var i int
				for i = 0; i < len(clusters); i++ {
					if clusters[i] == api.URL {
						break
					}
				}

				if i == len(clusters) {
					return
				}

				// operate pointer has no necessary to call update api
				bizInfo.method.URL = strings.Join(append(clusters[:i], clusters[i+1:]...), ",")
			} else {
				// double check, avoid removing api that does not exists
				if !strings.Contains(bizInfo.method.URL, api.URL) {
					return
				}
				// if backend has only one node, then just directly remove
				_, _ = rt.tree.Remove(key)
			}
		}
	}
}

func getTrieKey(method config.HTTPVerb, path string, isPrefix bool) string {
	if isPrefix {
		if !strings.HasSuffix(path, constant.PathSlash) {
			path = path + constant.PathSlash
		}
		path = path + "**"
	}
	return stringutil.GetTrieKey(string(method), path)
}

// PutAPI puts an api into the resource
func (rt *Route) PutAPI(api router.API) error {
	lowerCasePath := strings.ToLower(api.URLPattern)
	key := getTrieKey(api.HTTPVerb, lowerCasePath, false)
	node, ok := rt.getNode(key)
	if !ok {
		rn := &Node{
			fullPath: lowerCasePath,
			method:   &api.Method,
			headers:  api.Headers,
		}
		rt.lock.Lock()
		defer rt.lock.Unlock()
		_, _ = rt.tree.Put(key, rn)
		return nil
	}
	return errors.Errorf(
		"Method %s with address %s already exists in path %s",
		api.HTTPVerb, lowerCasePath, node.fullPath,
	)
}

// PutOrUpdateAPI puts or updates an api into the resource
func (rt *Route) PutOrUpdateAPI(api router.API) error {
	lowerCasePath := strings.ToLower(api.URLPattern)
	key := getTrieKey(api.HTTPVerb, lowerCasePath, false)
	rn := &Node{
		fullPath: lowerCasePath,
		method:   &api.Method,
		headers:  api.Headers,
	}
	rt.lock.Lock()
	defer rt.lock.Unlock()

	// if api is exists
	if exists, err := rt.tree.Contains(key); err != nil {
		return err
	} else if exists {
		// append new cluster to the node
		if node, _, exists, err := rt.tree.Get(key); err != nil {
			return err
		} else if exists {
			bizInfoInterface := node.GetBizInfo()
			bizInfo, ok := bizInfoInterface.(*Node)
			if bizInfo == nil || !ok {
				return errors.New("bizInfoInterface.(*Node) failed")
			}
			if !strings.Contains(bizInfo.method.URL, api.URL) {
				// operate pointer has no necessary to call update api
				bizInfo.method.URL = bizInfo.method.URL + "," + api.URL
			}
		}
	} else {
		if ok, err := rt.tree.PutOrUpdate(key, rn); !ok {
			return err
		}
	}

	return nil
}

// FindAPI return if api has path in trie,or nil
func (rt *Route) FindAPI(fullPath string, httpverb config.HTTPVerb) (*router.API, bool) {
	lowerCasePath := strings.ToLower(fullPath)
	key := getTrieKey(httpverb, lowerCasePath, false)
	if n, found := rt.getNode(key); found {
		rt.lock.RLock()
		defer rt.lock.RUnlock()
		return &router.API{
			URLPattern: n.fullPath,
			Method:     *n.method,
			Headers:    n.headers,
		}, found
	}
	return nil, false
}

// MatchAPI FindAPI returns the api that meets the rule
func (rt *Route) MatchAPI(fullPath string, httpverb config.HTTPVerb) (*router.API, bool) {
	lowerCasePath := strings.ToLower(fullPath)
	key := getTrieKey(httpverb, lowerCasePath, false)
	if n, found := rt.matchNode(key); found {
		rt.lock.RLock()
		defer rt.lock.RUnlock()
		return &router.API{
			URLPattern: n.fullPath,
			Method:     *n.method,
			Headers:    n.headers,
		}, found
	}
	return nil, false
}

// DeleteNode delete node by fullPath
func (rt *Route) DeleteNode(fullPath string) bool {
	rt.lock.RLock()
	defer rt.lock.RUnlock()
	methodList := [8]config.HTTPVerb{"ANY", "GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}
	for _, v := range methodList {
		key := getTrieKey(v, fullPath, false)
		_, _ = rt.tree.Remove(key)
	}
	return true
}

// DeleteAPI delete api by fullPath and http verb
func (rt *Route) DeleteAPI(fullPath string, httpverb config.HTTPVerb) bool {
	lowerCasePath := strings.ToLower(fullPath)
	key := getTrieKey(httpverb, lowerCasePath, false)
	if _, found := rt.getNode(key); found {
		rt.lock.RLock()
		defer rt.lock.RUnlock()
		_, _ = rt.tree.Remove(key)
		return true
	}
	return false
}

func (rt *Route) getNode(fullPath string) (*Node, bool) {
	var n any
	var found bool
	rt.lock.RLock()
	defer rt.lock.RUnlock()
	trieNode, _, _, _ := rt.tree.Get(fullPath)
	found = trieNode != nil
	if !found {
		return nil, false
	}
	n = trieNode.GetBizInfo()
	if n == nil {
		return nil, false
	}
	return n.(*Node), found
}

func (rt *Route) matchNode(fullPath string) (*Node, bool) {
	var n any
	var found bool
	rt.lock.RLock()
	defer rt.lock.RUnlock()
	trieNode, _, _ := rt.tree.Match(fullPath)
	found = trieNode != nil
	if !found {
		return nil, false
	}
	n = trieNode.GetBizInfo()
	if n == nil {
		return nil, false
	}
	return n.(*Node), found
}

func wildcardMatch(wildcardPath string, checkPath string) url.Values {

	cPaths := strings.Split(strings.TrimLeft(checkPath, constant.PathSlash), constant.PathSlash)
	wPaths := strings.Split(strings.TrimLeft(wildcardPath, constant.PathSlash), constant.PathSlash)
	result := url.Values{}
	if len(cPaths) == 0 || len(wPaths) == 0 || len(cPaths) != len(wPaths) {
		return nil
	}
	for i := 0; i < len(cPaths); i++ {
		if !strings.EqualFold(cPaths[i], wPaths[i]) && !strings.HasPrefix(wPaths[i], constant.PathParamIdentifier) {
			return nil
		}
		if strings.HasPrefix(wPaths[i], constant.PathParamIdentifier) {
			result.Add(strings.TrimPrefix(wPaths[i], constant.PathParamIdentifier), cPaths[i])
		}
	}
	return result
}

// NewRoute returns an empty router tree
func NewRoute() *Route {
	return &Route{
		tree: trie.NewTrie(),
	}
}
