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

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 netobjgroups

import (
	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
	"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/sdn/netobjgroups"
)

func flattenNetworkObjectGroupResource(d *schema.ResourceData, rec *netobjgroups.RecordNetObjGroup) {
	d.Set("name", rec.Name)
	d.Set("access_group_id", rec.AccessGroupID)
	d.Set("access_group_name", rec.AccessGroupName)
	d.Set("description", rec.Description)
	d.Set("type", rec.Type)
	d.Set("purpose", rec.Purpose)
	d.Set("version_id", int(rec.VersionID))
	d.Set("addresses", flattenAddresses(rec.Addresses))
	d.Set("counters", flattenCounters(rec.Counters))
	d.Set("l2_connection_ports", flattenL2ConnectionPorts(rec.L2ConnectionPorts))
	d.Set("logical_ports", flattenLogicalPorts(rec.LogicalPorts))
	d.Set("external_network_ports", flattenExternalNetworkPorts(rec.ExternalNetworkPorts))
	d.Set("security_policies", flattenSecurityPolicies(rec.SecurityPolicies))
}

func flattenNetworkObjectGroupDataSource(d *schema.ResourceData, rec *netobjgroups.RecordNetObjGroup) {
	d.Set("name", rec.Name)
	d.Set("access_group_id", rec.AccessGroupID)
	d.Set("access_group_name", rec.AccessGroupName)
	d.Set("description", rec.Description)
	d.Set("type", rec.Type)
	d.Set("purpose", rec.Purpose)
	d.Set("version_id", int(rec.VersionID))
	d.Set("created_at", rec.CreatedAt)
	d.Set("updated_at", rec.UpdatedAt)
	d.Set("addresses", flattenAddresses(rec.Addresses))
	d.Set("counters", flattenCounters(rec.Counters))
	d.Set("l2_connection_ports", flattenL2ConnectionPorts(rec.L2ConnectionPorts))
	d.Set("logical_ports", flattenLogicalPorts(rec.LogicalPorts))
	d.Set("external_network_ports", flattenExternalNetworkPorts(rec.ExternalNetworkPorts))
	d.Set("security_policies", flattenSecurityPolicies(rec.SecurityPolicies))
}

func flattenAddresses(addrs netobjgroups.NetAddresses) []map[string]interface{} {
	res := make([]map[string]interface{}, 0, len(addrs))
	for _, a := range addrs {
		res = append(res, map[string]interface{}{
			"id":                a.ID,
			"net_address_type":  a.NetAddressType,
			"ip_addr":           a.IPAddr,
			"ip_addr_range_end": a.IPAddrRangeEnd,
			"ip_prefix":         a.IPPrefix,
			"mac_addr":          a.MACAddr,
		})
	}
	return res
}

func flattenCounters(c netobjgroups.Counter) []map[string]interface{} {
	return []map[string]interface{}{
		{
			"addresses_count":           int(c.AddressesCount),
			"l2_connection_ports_count": int(c.L2ConnectionPortsCount),
			"logical_ports_count":       int(c.LogicalPortsCount),
			"security_policies_count":   int(c.SecurityPoliciesCount),
			"security_rules_count":      int(c.SecurityRulesCount),
		},
	}
}

func flattenStatus(s netobjgroups.Status) []map[string]interface{} {
	return []map[string]interface{}{
		{
			"operation_status":  s.OperationStatus,
			"hypervisor_status": s.HypervisorStatus,
			"hypervisors":       flattenHypervisorsInfo(s.Hypervisors),
		},
	}
}

func flattenHypervisorsInfo(hvs netobjgroups.HypervisorsInfo) []map[string]interface{} {
	res := make([]map[string]interface{}, 0, len(hvs))
	for _, hv := range hvs {
		res = append(res, map[string]interface{}{
			"operation_status":  hv.OperationStatus,
			"name":              hv.Name,
			"display_name":      hv.DisplayName,
			"hypervisor_status": hv.HypervisorStatus,
			"synced_at":         hv.SyncedAt,
		})
	}
	return res
}

func flattenLogicalPortAddresses(addrs netobjgroups.LogicalPortAddresses) []map[string]interface{} {
	res := make([]map[string]interface{}, 0, len(addrs))
	for _, a := range addrs {
		res = append(res, map[string]interface{}{
			"ip":              a.IP,
			"ip_type":         a.IPType,
			"is_discovered":   a.IsDiscovered,
			"is_primary":      a.IsPrimary,
			"mac":             a.MAC,
			"id":              a.ID,
			"logical_port_id": a.LogicalPortID,
			"assigned_at":     a.AssignedAt,
		})
	}
	return res
}

func flattenBindings(b netobjgroups.Bindings) []map[string]interface{} {
	return []map[string]interface{}{
		{
			"id":                     b.ID,
			"segment_display_name":   b.SegmentDisplayName,
			"segment_id":             b.SegmentID,
			"port_security":          b.PortSecurity,
			"address_detection":      b.AddressDetection,
			"version_id":             int(b.VersionID),
			"created_at":             b.CreatedAt,
			"updated_at":             b.UpdatedAt,
			"logical_port_addresses": flattenLogicalPortAddresses(b.LogicalPortAddresses),
		},
	}
}

func flattenExcludeFirewall(e netobjgroups.ExcludeFirewall) []map[string]interface{} {
	return []map[string]interface{}{
		{
			"exclusion_reason":                e.ExclusionReason,
			"logical_port_addresses_excluded": e.LogicalPortAddressesExcluded,
			"logical_port_excluded":           e.LogicalPortExcluded,
		},
	}
}

func flattenLabels(l netobjgroups.Labels) []map[string]interface{} {
	return []map[string]interface{}{
		{
			"vm_id":   l.VMID,
			"vm_name": l.VMName,
		},
	}
}

func flattenLogicalPort(lp netobjgroups.LogicalPort) map[string]interface{} {
	return map[string]interface{}{
		"id":                       lp.ID,
		"access_group_id":          lp.AccessGroupID,
		"access_group_name":        lp.AccessGroupName,
		"adapter_mac":              lp.AdapterMAC,
		"address_detection":        lp.AddressDetection,
		"description":              lp.Description,
		"created_at":               lp.CreatedAt,
		"display_name":             lp.DisplayName,
		"enabled":                  lp.Enabled,
		"exclude_firewall":         flattenExcludeFirewall(lp.ExcludeFirewall),
		"external_network_id":      lp.ExternalNetworkID,
		"hypervisor":               lp.Hypervisor,
		"hypervisor_display_name":  lp.HypervisorDisplayName,
		"labels":                   flattenLabels(lp.Labels),
		"live_migration_target_hv": lp.LiveMigrationTargetHV,
		"status":                   flattenStatus(lp.Status),
		"bindings":                 flattenBindings(lp.Bindings),
		"unique_identifier":        lp.UniqueIdentifier,
		"updated_at":               lp.UpdatedAt,
		"version_id":               int(lp.VersionID),
	}
}

func flattenLogicalPorts(ports netobjgroups.LogicalPorts) []map[string]interface{} {
	res := make([]map[string]interface{}, 0, len(ports))
	for _, lp := range ports {
		res = append(res, flattenLogicalPort(lp))
	}
	return res
}

func flattenL2ExternalNetwork(n netobjgroups.L2ExternalNetwork) []map[string]interface{} {
	hypervisors := make([]interface{}, 0, len(n.Hypervisors))
	for _, h := range n.Hypervisors {
		hypervisors = append(hypervisors, h)
	}
	vlanTag := 0
	if n.VLANTag != nil {
		vlanTag = *n.VLANTag
	}
	return []map[string]interface{}{
		{
			"bridge_network_name": n.BridgeNetworkName,
			"created_at":          n.CreatedAt,
			"description":         n.Description,
			"display_name":        n.DisplayName,
			"hypervisors":         hypervisors,
			"id":                  n.ID,
			"updated_at":          n.UpdatedAt,
			"version_id":          int(n.VersionID),
			"vlan_tag":            vlanTag,
		},
	}
}

func flattenL2ConnectionPorts(ports netobjgroups.L2ConnectionPorts) []map[string]interface{} {
	res := make([]map[string]interface{}, 0, len(ports))
	for _, p := range ports {
		res = append(res, map[string]interface{}{
			"id":                  p.ID,
			"access_group_id":     p.AccessGroupID,
			"created_at":          p.CreatedAt,
			"updated_at":          p.UpdatedAt,
			"version_id":          int(p.VersionID),
			"l2_external_network": flattenL2ExternalNetwork(p.L2ExternalNetwork),
		})
	}
	return res
}

func flattenIPv6Config(c netobjgroups.IPv6Config) []map[string]interface{} {
	return []map[string]interface{}{
		{
			"address_mode":       c.AddressMode,
			"enable_periodic_ra": c.EnablePeriodicRA,
			"interval_ra":        int(c.IntervalRA),
			"router_preference":  c.RouterPreference,
		},
	}
}

func flattenRouterGatewayPort(r netobjgroups.RouterGateawayPort) []map[string]interface{} {
	return []map[string]interface{}{
		{
			"created_at":          r.CreatedAt,
			"description":         r.Description,
			"id":                  r.ID,
			"router_display_name": r.RouterDisplayName,
			"router_id":           r.RouterID,
			"snat_enabled":        r.SNATEnabled,
			"updated_at":          r.UpdatedAt,
		},
	}
}

func flattenSegment(s netobjgroups.Segment) []map[string]interface{} {
	return []map[string]interface{}{
		{
			"access_group_id":   s.AccessGroupID,
			"access_group_name": s.AccessGroupName,
			"created_at":        s.CreatedAt,
			"description":       s.Description,
			"display_name":      s.DisplayName,
			"enabled":           s.Enabled,
			"id":                s.ID,
			"subnet_v4":         s.SubnetV4,
			"subnet_v6":         s.SubnetV6,
			"updated_at":        s.UpdatedAt,
			"version_id":        int(s.VersionID),
		},
	}
}

func flattenRouter(r netobjgroups.Router) []map[string]interface{} {
	gateawayPorts := make([]map[string]interface{}, 0, len(r.GateawayPorts))
	for _, gp := range r.GateawayPorts {
		gateawayPorts = append(gateawayPorts, map[string]interface{}{
			"created_at":           gp.CreatedAt,
			"description":          gp.Description,
			"external_l4_port_max": int(gp.ExternalL4PortMax),
			"external_l4_port_min": int(gp.ExternalL4PortMin),
			"id":                   gp.ID,
			"snat_enabled":         gp.SNATEnabled,
			"status":               flattenStatus(gp.Status),
			"updated_at":           gp.UpdatedAt,
			"version_id":           int(gp.VersionID),
		})
	}

	policies := make([]map[string]interface{}, 0, len(r.Policies))
	for _, pol := range r.Policies {
		nextIPv4 := make([]interface{}, 0, len(pol.NextIPv4Address))
		for _, ip := range pol.NextIPv4Address {
			nextIPv4 = append(nextIPv4, ip)
		}
		nextIPv6 := make([]interface{}, 0, len(pol.NextIPv6Address))
		for _, ip := range pol.NextIPv6Address {
			nextIPv6 = append(nextIPv6, ip)
		}
		policies = append(policies, map[string]interface{}{
			"action":            pol.Action,
			"created_at":        pol.CreatedAt,
			"display_name":      pol.DisplayName,
			"enabled":           pol.Enabled,
			"id":                pol.ID,
			"match":             pol.Match,
			"next_ipv4_address": nextIPv4,
			"next_ipv6_address": nextIPv6,
			"priority":          pol.Priority,
			"updated_at":        pol.UpdatedAt,
			"version_id":        int(pol.VersionID),
		})
	}

	ports := make([]map[string]interface{}, 0, len(r.Port))
	for _, port := range r.Port {
		ports = append(ports, map[string]interface{}{
			"created_at":   port.CreatedAt,
			"description":  port.Description,
			"enabled":      port.Enabled,
			"id":           port.ID,
			"ipv4_address": port.IPv4Address,
			"ipv6_address": port.IPv6Address,
			"ipv6_config":  flattenIPv6Config(port.IPv6Config),
			"mac":          port.MAC,
			"segment_id":   port.SegmentID,
			"segment":      flattenSegment(port.Segment),
			"status":       flattenStatus(port.Status),
			"updated_at":   port.UpdatedAt,
			"version_id":   int(port.VersionID),
		})
	}

	return []map[string]interface{}{
		{
			"access_group_id":   r.AccessGroupID,
			"access_group_name": r.AccessGroupName,
			"created_at":        r.CreatedAt,
			"description":       r.Description,
			"display_name":      r.DisplayName,
			"enabled":           r.Enabled,
			"gateaway_ports":    gateawayPorts,
			"id":                r.ID,
			"policies":          policies,
			"ports":             ports,
			"status":            flattenStatus(r.Status),
			"updated_at":        r.UpdatedAt,
			"version_id":        int(r.VersionID),
		},
	}
}

func flattenFloatingIP(f netobjgroups.FloatingIP) []map[string]interface{} {
	return []map[string]interface{}{
		{
			"access_group_id":   f.AccessGroupID,
			"access_group_name": f.AccessGroupName,
			"created_at":        f.CreatedAt,
			"updated_at":        f.UpdatedAt,
			"version_id":        int(f.VersionID),
			"logical_port":      []map[string]interface{}{flattenLogicalPort(f.LogicalPort)},
			"router":            flattenRouter(f.Router),
		},
	}
}

func flattenExternalNetworkPortFields(ports netobjgroups.ExternalNetworkPortsField) []map[string]interface{} {
	res := make([]map[string]interface{}, 0, len(ports))
	for _, p := range ports {
		res = append(res, map[string]interface{}{
			"access_group_id":      p.AccessGroupID,
			"access_group_name":    p.AccessGroupName,
			"comment":              p.Comment,
			"display_name":         p.DisplayName,
			"enabled":              p.Enabled,
			"ipv4":                 p.IPv4,
			"ipv6":                 p.IPv6,
			"ipv6_config":          flattenIPv6Config(p.IPv6Config),
			"mac":                  p.MAC,
			"router_gateaway_port": flattenRouterGatewayPort(p.RouterGateawayPort),
			"floating_ip":          flattenFloatingIP(p.FloatingIP),
		})
	}
	return res
}

func flattenExternalNetworkPorts(ports netobjgroups.ExternalNetworkPorts) []map[string]interface{} {
	res := make([]map[string]interface{}, 0, len(ports))
	for _, p := range ports {
		hypervisors := make([]interface{}, 0, len(p.Hypervisors))
		for _, h := range p.Hypervisors {
			hypervisors = append(hypervisors, h)
		}
		res = append(res, map[string]interface{}{
			"id":                     p.ID,
			"access_group_id":        p.AccessGroupID,
			"access_group_name":      p.AccessGroupName,
			"bridge_network_name":    p.BridgeNetworkName,
			"comment":                p.Comment,
			"default_gateway_ipv4":   p.DefaultGatewayIPv4,
			"default_gateway_ipv6":   p.DefaultGatewayIPv6,
			"description":            p.Description,
			"enabled":                p.Enabled,
			"external_network_ports": flattenExternalNetworkPortFields(p.ExternalNetworkPorts),
			"hypervisors":            hypervisors,
			"ipv4":                   p.IPv4,
			"status":                 flattenStatus(p.Status),
			"version_id":             int(p.VersionID),
			"subnet_v4":              p.SubnetV4,
			"subnet_v6":              p.SubnetV6,
			"created_at":             p.CreatedAt,
			"updated_at":             p.UpdatedAt,
			"vlan_tag":               p.VLANTag,
			"mac":                    p.MAC,
		})
	}
	return res
}

func flattenAppliedNetObjectGroups(groups netobjgroups.AppliedNetObjectGroups) []map[string]interface{} {
	res := make([]map[string]interface{}, 0, len(groups))
	for _, g := range groups {
		res = append(res, map[string]interface{}{
			"id":         g.ID,
			"name":       g.Name,
			"version_id": int(g.VersionID),
		})
	}
	return res
}

func flattenSecurityRules(rules netobjgroups.SecurityRules) []map[string]interface{} {
	res := make([]map[string]interface{}, 0, len(rules))
	for _, r := range rules {
		srcNetObj := []map[string]interface{}{}
		if r.SourceNetObject != nil {
			srcNetObj = []map[string]interface{}{
				{
					"display_name":        r.SourceNetObject.DisplayName,
					"net_address_pool_id": r.SourceNetObject.NetAddressPoolID,
					"net_object_group_id": r.SourceNetObject.NetObjectGroupID,
				},
			}
		}
		dstNetObj := []map[string]interface{}{}
		if r.DestinationNetObject != nil {
			dstNetObj = []map[string]interface{}{
				{
					"display_name":        r.DestinationNetObject.DisplayName,
					"net_address_pool_id": r.DestinationNetObject.NetAddressPoolID,
					"net_object_group_id": r.DestinationNetObject.NetObjectGroupID,
				},
			}
		}
		filter := []map[string]interface{}{}
		if r.Filter != nil {
			tcpDstPorts := make([]interface{}, 0, len(r.Filter.Filters.TCPDstPorts))
			for _, p := range r.Filter.Filters.TCPDstPorts {
				tcpDstPorts = append(tcpDstPorts, p)
			}
			udpDstPorts := make([]interface{}, 0, len(r.Filter.Filters.UDPDstPorts))
			for _, p := range r.Filter.Filters.UDPDstPorts {
				udpDstPorts = append(udpDstPorts, p)
			}
			filter = []map[string]interface{}{
				{
					"name": r.Filter.Name,
					"filters": []map[string]interface{}{
						{
							"all":                  r.Filter.Filters.All,
							"arp":                  r.Filter.Filters.ARP,
							"dhcp":                 r.Filter.Filters.DHCP,
							"expression":           r.Filter.Filters.Expression,
							"icmp":                 r.Filter.Filters.ICMP,
							"ip":                   r.Filter.Filters.IP,
							"ip_v4":                r.Filter.Filters.IPv4,
							"ip_v6":                r.Filter.Filters.IPv6,
							"keep_opened_sessions": r.Filter.Filters.KeepOpenedSessions,
							"nd":                   r.Filter.Filters.ND,
							"tcp":                  r.Filter.Filters.TCP,
							"tcp_dst_ports":        tcpDstPorts,
							"udp":                  r.Filter.Filters.UDP,
							"udp_dst_ports":        udpDstPorts,
						},
					},
				},
			}
		}
		res = append(res, map[string]interface{}{
			"access_group_id":        r.AccessGroupID,
			"action":                 r.Action,
			"description":            r.Description,
			"destination_net_object": dstNetObj,
			"direction":              r.Direction,
			"display_name":           r.DisplayName,
			"enabled":                r.Enabled,
			"filter":                 filter,
			"id":                     r.ID,
			"log_enabled":            r.LogEnabled,
			"log_name":               r.LogName,
			"log_severity":           r.LogSeverity,
			"priority":               r.Priority,
			"security_policy_id":     r.SecurityPolicyID,
			"source_net_object":      srcNetObj,
			"statistics_enabled":     r.StatisticsEnabled,
			"type":                   r.Type,
			"version_id":             int(r.VersionID),
		})
	}
	return res
}

func flattenSecurityPolicies(policies netobjgroups.SecurityPolicies) []map[string]interface{} {
	res := make([]map[string]interface{}, 0, len(policies))
	for _, p := range policies {
		res = append(res, map[string]interface{}{
			"access_group_id":           p.AccessGroupID,
			"access_group_name":         p.AccessGroupName,
			"applied_net_object_groups": flattenAppliedNetObjectGroups(p.AppliedNetObjectGroups),
			"created_at":                p.CreatedAt,
			"description":               p.Description,
			"display_name":              p.DisplayName,
			"enabled":                   p.Enabled,
			"end_priority":              int(p.EndPriority),
			"id":                        p.ID,
			"security_rules":            flattenSecurityRules(p.SecurityRules),
			"start_priority":            int(p.StartPriority),
			"status":                    flattenStatus(p.Status),
			"type":                      p.Type,
			"version_id":                int(p.VersionID),
			"updated_at":                p.UpdatedAt,
		})
	}
	return res
}

func flattenNetworkObjectGroupList(list *netobjgroups.NetObjGroupList) []map[string]interface{} {
	if list == nil {
		return []map[string]interface{}{}
	}
	res := make([]map[string]interface{}, 0, len(list.Objects))
	for _, v := range list.Objects {
		res = append(res, map[string]interface{}{
			"id":                     v.ID,
			"name":                   v.Name,
			"access_group_id":        v.AccessGroupID,
			"access_group_name":      v.AccessGroupName,
			"description":            v.Description,
			"type":                   v.Type,
			"purpose":                v.Purpose,
			"version_id":             int(v.VersionID),
			"created_at":             v.CreatedAt,
			"updated_at":             v.UpdatedAt,
			"addresses":              flattenAddresses(v.Addresses),
			"counters":               flattenCounters(v.Counters),
			"l2_connection_ports":    flattenL2ConnectionPorts(v.L2ConnectionPorts),
			"logical_ports":          flattenLogicalPorts(v.LogicalPorts),
			"external_network_ports": flattenExternalNetworkPorts(v.ExternalNetworkPorts),
			"security_policies":      flattenSecurityPolicies(v.SecurityPolicies),
		})
	}
	return res
}
