// Copyright 2022 The Amesh Authors
//
// Licensed 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 utils

import (
	"bytes"
	"encoding/json"

	"github.com/api7/gopkg/pkg/log"
	"go.uber.org/zap"

	"github.com/api7/amesh/pkg/apisix"
)

// CompareRoutes diffs two Route array and finds the new adds, updates
// and deleted ones. Note it stands on the first Route array's point
// of view.
func CompareRoutes(r1, r2 []*apisix.Route) (added, deleted, updated []*apisix.Route) {
	if r1 == nil {
		return r2, nil, nil
	}
	if r2 == nil {
		return nil, r1, nil
	}

	r1Map := make(map[string]*apisix.Route)
	r2Map := make(map[string]*apisix.Route)
	for _, r := range r1 {
		r1Map[r.Id] = r
	}
	for _, r := range r2 {
		r2Map[r.Id] = r
	}
	for _, r := range r2 {
		if _, ok := r1Map[r.Id]; !ok {
			added = append(added, r)
		}
	}
	for _, ro := range r1 {
		if rn, ok := r2Map[ro.Id]; !ok {
			deleted = append(deleted, ro)
		} else {
			roJson, errO := json.Marshal(ro)
			rnJson, errN := json.Marshal(rn)
			if errO != nil || errN != nil {
				log.Errorw("compare route: marshal failed",
					zap.NamedError("error_old", errO),
					zap.NamedError("error_new", errN),
				)
				updated = append(updated, rn)
			} else if !bytes.Equal(roJson, rnJson) {
				updated = append(updated, rn)
			}
		}
	}
	return
}

// CompareUpstreams diffs two Upstreams array and finds the new adds, updates
// and deleted ones. Note it stands on the first Upstream array's point
// of view.
func CompareUpstreams(u1, u2 []*apisix.Upstream) (added, deleted, updated []*apisix.Upstream) {
	if u1 == nil {
		return u2, nil, nil
	}
	if u2 == nil {
		return nil, u1, nil
	}
	u1Map := make(map[string]*apisix.Upstream)
	u2Map := make(map[string]*apisix.Upstream)
	for _, u := range u1 {
		u1Map[u.Id] = u
	}
	for _, u := range u2 {
		u2Map[u.Id] = u
	}
	for _, u := range u2 {
		if _, ok := u1Map[u.Id]; !ok {
			added = append(added, u)
		}
	}
	for _, uo := range u1 {
		if un, ok := u2Map[uo.Id]; !ok {
			deleted = append(deleted, uo)
		} else {
			uoJson, errO := json.Marshal(uo)
			unJson, errN := json.Marshal(un)
			if errO != nil || errN != nil {
				log.Errorw("compare upstream: marshal failed",
					zap.NamedError("error_old", errO),
					zap.NamedError("error_new", errN),
				)
				updated = append(updated, un)
			} else if !bytes.Equal(uoJson, unJson) {
				updated = append(updated, un)
			}
		}
	}
	return
}

// IsSameNodes checks nodes host, port and weight. It doesn't check metadata.
func IsSameNodes(nodes1, nodes2 []*apisix.Node) bool {
	if nodes1 == nil && nodes2 == nil {
		return true
	}
	if nodes1 == nil && nodes2 != nil ||
		nodes1 != nil && nodes2 == nil {
		return false
	}
	if len(nodes1) != len(nodes2) {
		return false
	}

	n1Map := map[string]*apisix.Node{}
	for _, u := range nodes1 {
		n1Map[u.Host] = u
	}

	for _, n2 := range nodes2 {
		n1, ok := n1Map[n2.Host]
		if !ok {
			return false
		}
		if n1.Port != n2.Port {
			return false
		}
		if n1.Weight != n2.Weight {
			return false
		}

		// TODO: Check metadata
	}

	return true
}
