/*
Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved.
Authors:
Petr Krutov, <petr.krutov@digitalenergy.online>
Stanislav Solovev, <spsolovev@digitalenergy.online>
Kasim Baybikov, <kmbaybikov@basistech.ru>
Tim Tkachev, <tvtkachev@basistech.ru>

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

/*
Terraform DECORT provider - manage resources provided by DECORT (Digital Energy Cloud
Orchestration Technology) with Terraform by Hashicorp.

Source code: https://repository.basistech.ru/BASIS/terraform-provider-decort

Please see README.md to learn where to place source code so that it
builds seamlessly.

Documentation: https://repository.basistech.ru/BASIS/terraform-provider-decort/wiki
*/

package kvmvm

import (
	"bytes"
	"hash/fnv"

	log "github.com/sirupsen/logrus"
	"repository.basistech.ru/BASIS/terraform-provider-decort/internal/statefuncs"

	"sort"

	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)

// This is subresource of compute resource used when creating/managing compute network connections

func networkSubresIPAddreDiffSupperss(key, oldVal, newVal string, d *schema.ResourceData) bool {
	if newVal != "" && newVal != oldVal {
		log.Debugf("networkSubresIPAddreDiffSupperss: key=%s, oldVal=%q, newVal=%q -> suppress=FALSE", key, oldVal, newVal)
		return false
	}
	log.Debugf("networkSubresIPAddreDiffSupperss: key=%s, oldVal=%q, newVal=%q -> suppress=TRUE", key, oldVal, newVal)
	return true // suppress difference
}

// This function is based on the original Terraform SerializeResourceForHash found
// in helper/schema/serialize.go
// It skips network subresource attributes, which are irrelevant for identification
// of unique network blocks
func networkSubresourceSerialize(output *bytes.Buffer, val interface{}, resource *schema.Resource) {
	if val == nil {
		return
	}

	rs := resource.Schema
	m := val.(map[string]interface{})

	keys := make([]string, 0, len(rs))
	allComputed := true

	for k, val := range rs {
		if val.Optional || val.Required {
			allComputed = false
		}

		keys = append(keys, k)
	}

	sort.Strings(keys)
	for _, k := range keys {
		// explicitly ignore "ip_address" when hashing
		if k == "ip_address" {
			continue
		}

		subSchema := rs[k]
		// Skip attributes that are not user-provided. Computed attributes
		// do not contribute to the hash since their ultimate value cannot
		// be known at plan/diff time.
		if !allComputed && !(subSchema.Required || subSchema.Optional) {
			continue
		}

		output.WriteString(k)
		output.WriteRune(':')
		value := m[k]
		schema.SerializeValueForHash(output, value, subSchema)
	}
}

// HashNetworkSubresource hashes network subresource of compute resource. It uses
// specially designed networkSubresourceSerialize (see above) to make sure hashing
// does not involve attributes that we deem irrelevant to the uniqueness of network
// subresource definitions.
// It is this function that should be specified as SchemaSetFunc when creating Set
// from network subresource (e.g. in flattenCompute)
//
// This function is based on the original Terraform function HashResource from
// helper/schema/set.go
func HashNetworkSubresource(resource *schema.Resource) schema.SchemaSetFunc {
	return func(v interface{}) int {
		var serialized bytes.Buffer
		networkSubresourceSerialize(&serialized, v, resource)

		hs := fnv.New32a()
		hs.Write(serialized.Bytes())
		return int(hs.Sum32())
	}
}

func networkSubresourceSchemaMake() map[string]*schema.Schema {
	rets := map[string]*schema.Schema{
		"net_type": {
			Type:         schema.TypeString,
			Required:     true,
			StateFunc:    statefuncs.StateFuncToUpper,
			ValidateFunc: validation.StringInSlice([]string{"EXTNET", "VINS", "VFNIC", "DPDK", "SDN", "TRUNK"}, false), // observe case while validating
			Description:  "Type of the network for this connection",
		},

		"net_id": {
			Type:        schema.TypeInt,
			Required:    true,
			Description: "ID of the network for this connection.",
		},

		"ip_address": {
			Type:             schema.TypeString,
			Optional:         true,
			Computed:         true,
			DiffSuppressFunc: networkSubresIPAddreDiffSupperss,
			Description:      "Optional IP address to assign to this connection. This IP should belong to the selected network and free for use.",
		},

		"mac": {
			Type:             schema.TypeString,
			Optional:         true,
			Computed:         true,
			DiffSuppressFunc: networkSubresIPAddreDiffSupperss,
			Description:      "MAC address associated with this connection. MAC address is assigned automatically.",
		},

		"weight": {
			Type:        schema.TypeInt,
			Optional:    true,
			Computed:    true,
			Description: "weight the network if you need to sort network list, the smallest attach first. zero or null weight attach last",
		},

		"mtu": {
			Type:     schema.TypeInt,
			Optional: true,
			Computed: true,
			//Default:      1500,
			ValidateFunc: validation.IntBetween(1500, 9216),
			Description:  "Maximum transmission unit, used only for DPDK type, must be 1500-9216",
		},

		"sdn_interface_id": {
			Type:             schema.TypeString,
			Optional:         true,
			Computed:         true,
			DiffSuppressFunc: networkSubresIPAddreDiffSupperss,
			Description:      "unique_identifier of LogicalPort on SDN side",
		},

		"enabled": {
			Type:        schema.TypeBool,
			Optional:    true,
			Computed:    true,
			Description: "network enable flag",
		},

		"net_mask": {
			Type:        schema.TypeInt,
			Optional:    true,
			Computed:    true,
			Description: "Subnet mask, used only for DPDK and VFNIC network types",
		},
	}
	return rets
}
