package utilities

import (
	"github.com/hashicorp/terraform-plugin-framework/attr"
	"github.com/hashicorp/terraform-plugin-framework/types"
)

// differenceSimpleType returns lists added and removed values
func differenceSimpleType(oldSet, newSet types.Set) (added, removed []any) {
	oldMap := make(map[interface{}]struct{})
	newMap := make(map[interface{}]struct{})

	for _, elem := range oldSet.Elements() {
		oldMap[elem] = struct{}{}
	}

	for _, elem := range newSet.Elements() {
		newMap[elem] = struct{}{}
	}

	for elem := range newMap {
		if _, found := oldMap[elem]; !found {
			added = append(added, elem)
		}
	}

	for elem := range oldMap {
		if _, found := newMap[elem]; !found {
			removed = append(removed, elem)
		}
	}

	return
}

func differenceNetwork(oldSet, newSet types.Set) (added, changeIp, changeMac, removed []map[string]attr.Value) {
	oldSlice := oldSet.Elements()
	newSlice := newSet.Elements()

	added = make([]map[string]attr.Value, 0)
	changeIp = make([]map[string]attr.Value, 0)
	changeMac = make([]map[string]attr.Value, 0)
	removed = make([]map[string]attr.Value, 0)

	for _, oldNetwork := range oldSlice {
		oldMap := oldNetwork.(types.Object).Attributes()
		found := false
		for _, newNetwork := range newSlice {
			newMap := newNetwork.(types.Object).Attributes()
			if newMap["net_type"] == oldMap["net_type"] && newMap["net_id"] == oldMap["net_id"] && newMap["weight"] == oldMap["weight"] && (newMap["mtu"] == oldMap["mtu"] || newMap["mtu"].(types.Int64).ValueInt64() == 0) {
				found = true
				switch {
				case (newMap["net_type"].(types.String).ValueString() == "EXTNET" || newMap["net_type"].(types.String).ValueString() == "VINS") && (newMap["ip_address"] != oldMap["ip_address"] && newMap["ip_address"].(types.String).ValueString() != ""):
					changeIp = append(changeIp, newMap)
					fallthrough
				case newMap["mac"] != oldMap["mac"] && newMap["mac"].(types.String).ValueString() != "":
					newMap["old_mac"] = oldMap["mac"]
					changeMac = append(changeMac, newMap)
				}
			}
			if found {
				break
			}
		}
		if found {
			continue
		}
		removed = append(removed, oldMap)
	}

	for _, newNetwork := range newSlice {
		newMap := newNetwork.(types.Object).Attributes()
		found := false
		for _, oldNetwork := range oldSlice {
			oldMap := oldNetwork.(types.Object).Attributes()
			if newMap["net_type"] == oldMap["net_type"] && newMap["net_id"] == oldMap["net_id"] && newMap["weight"] == oldMap["weight"] && (newMap["mtu"] == oldMap["mtu"] || newMap["mtu"].(types.Int64).ValueInt64() == 0) {
				found = true
				break
			}
		}
		if found {
			continue
		}
		added = append(added, newMap)
	}

	return
}

func differenceAffinityRules(oldSet, newSet types.Set) (added, removed []any) {
	oldSlice := oldSet.Elements()
	newSlice := newSet.Elements()
	foundIdx := make([]bool, len(oldSlice))

	for _, newElem := range newSlice {
		newObj := newElem.(types.Object)
		newElemMap := newObj.Attributes()
		found := false
		for i, oldElem := range oldSlice {
			oldObj := oldElem.(types.Object)
			oldElemMap := oldObj.Attributes()
			if oldElemMap["topology"] == newElemMap["topology"] && oldElemMap["policy"] == newElemMap["policy"] &&
				oldElemMap["mode"] == newElemMap["mode"] && oldElemMap["key"] == newElemMap["key"] {
				valueOld, okOld := oldElemMap["value"]
				valueNew, okNew := newElemMap["value"]
				if !okNew && !okOld || valueOld == valueNew {
					found = true
					foundIdx[i] = true
					break
				}
			}
		}
		if !found {
			added = append(added, newElem)
		}
	}

	for i, found := range foundIdx {
		if !found {
			removed = append(removed, oldSlice[i])
		}
	}

	return
}

func differenceTags(oldSet, newSet types.Set) (added, removed []any) {
	oldSlice := oldSet.Elements()
	newSlice := newSet.Elements()
	foundIdx := make([]bool, len(oldSlice))

	for _, newElem := range newSlice {
		newObj := newElem.(types.Object)
		newElemMap := newObj.Attributes()
		found := false
		for i, oldElem := range oldSlice {
			oldObj := oldElem.(types.Object)
			oldElemMap := oldObj.Attributes()
			if oldElemMap["key"] == newElemMap["key"] && oldElemMap["value"] == newElemMap["value"] {
				found = true
				foundIdx[i] = true
				break
			}
		}
		if !found {
			added = append(added, newElem)
		}
	}

	for i, found := range foundIdx {
		if !found {
			removed = append(removed, oldSlice[i])
		}
	}

	return
}

func differencePortForwarding(oldSet, newSet types.Set) (added, removed []any) {
	oldSlice := oldSet.Elements()
	newSlice := newSet.Elements()
	foundIdx := make([]bool, len(oldSlice))

	for _, newElem := range newSlice {
		newObj := newElem.(types.Object)
		newElemMap := newObj.Attributes()
		found := false
		for i, oldElem := range oldSlice {
			oldObj := oldElem.(types.Object)
			oldElemMap := oldObj.Attributes()
			if oldElemMap["public_port_start"] == newElemMap["public_port_start"] && oldElemMap["public_port_end"] == newElemMap["public_port_end"] &&
				oldElemMap["local_port"] == newElemMap["local_port"] && oldElemMap["proto"] == newElemMap["proto"] {
				found = true
				foundIdx[i] = true
				break
			}
		}
		if !found {
			added = append(added, newElem)
		}
	}

	for i, found := range foundIdx {
		if !found {
			removed = append(removed, oldSlice[i])
		}
	}

	return
}

func differenceUserAccess(oldSet, newSet types.Set) (added, removed []any) {
	oldSlice := oldSet.Elements()
	newSlice := newSet.Elements()
	foundIdx := make([]bool, len(oldSlice))

	for _, newElem := range newSlice {
		newObj := newElem.(types.Object)
		newElemMap := newObj.Attributes()
		found := false
		for i, oldElem := range oldSlice {
			oldObj := oldElem.(types.Object)
			oldElemMap := oldObj.Attributes()
			if oldElemMap["username"] == newElemMap["username"] && oldElemMap["access_type"] == newElemMap["access_type"] {
				found = true
				foundIdx[i] = true
				break
			}
		}
		if !found {
			added = append(added, newElem)
		}
	}

	for i, found := range foundIdx {
		if !found {
			removed = append(removed, oldSlice[i])
		}
	}

	return
}

func differenceSnapshot(oldSet, newSet types.Set) (added, removed []any) {
	oldSlice := oldSet.Elements()
	newSlice := newSet.Elements()
	foundIdx := make([]bool, len(oldSlice))

	for _, newElem := range newSlice {
		newObj := newElem.(types.Object)
		newElemMap := newObj.Attributes()
		found := false
		for i, oldElem := range oldSlice {
			oldObj := oldElem.(types.Object)
			oldElemMap := oldObj.Attributes()
			if oldElemMap["label"] == newElemMap["label"] {
				found = true
				foundIdx[i] = true
				break
			}
		}
		if !found {
			added = append(added, newElem)
		}
	}

	for i, found := range foundIdx {
		if !found {
			removed = append(removed, oldSlice[i])
		}
	}

	return
}
