package logicalports

import (
	"context"

	"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
	log "github.com/sirupsen/logrus"
	"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/sdn/logicalports"
	"repository.basistech.ru/BASIS/terraform-provider-decort/internal/constants"
	"repository.basistech.ru/BASIS/terraform-provider-decort/internal/controller"
)

func resourceLogicalPortCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
	log.Debugf("resourceLogicalPortCreate: called logical port with name %s",
		d.Get("display_name").(string))
	c := m.(*controller.ControllerCfg)

	req := logicalports.CreateRequest{
		AccessGroupID: d.Get("access_group_id").(string),
		Description:   d.Get("description").(string),
		DisplayName:   d.Get("display_name").(string),
		Enabled:       d.Get("enabled").(bool),
		Hypervisor:    d.Get("hypervisor").(string),
		PortSecurity:  d.Get("port_security").(bool),
		SegmentID:     d.Get("segment_id").(string),
	}

	if adapterMAC, ok := d.GetOk("adapter_mac"); ok {
		req.AdapterMAC = adapterMAC.(string)
	}
	if uniqueID, ok := d.GetOk("unique_identifier"); ok {
		req.UniqueIdentifier = uniqueID.(string)
	}
	if labelsRaw, ok := d.GetOk("labels"); ok {
		labelsList := labelsRaw.([]interface{})
		if len(labelsList) > 0 {
			labelsMap := labelsList[0].(map[string]interface{})
			req.Labels = logicalports.CreateLabels{
				VMID:   labelsMap["vm_id"].(string),
				VMName: labelsMap["vm_name"].(string),
			}
		}
	}
	if logicalPortAddresses, ok := d.GetOk("logical_port_addresses"); ok {
		logicalPortAddressesList := logicalPortAddresses.([]interface{})
		logicalPortsAddressesArr := make([]logicalports.LogicalPortAddress, 0)
		for _, logicalPortAddressRaw := range logicalPortAddressesList {
			logicalPortAddressMap := logicalPortAddressRaw.(map[string]interface{})
			logicalPortAddress := logicalports.LogicalPortAddress{
				IP:        logicalPortAddressMap["ip"].(string),
				IPType:    logicalPortAddressMap["ip_type"].(string),
				IsPrimary: logicalPortAddressMap["is_primary"].(bool),
			}
			if isDiscovered, ok := logicalPortAddressMap["is_discovered"]; ok {
				logicalPortAddress.IsDiscovered = isDiscovered.(bool)
			}
			if mac, ok := logicalPortAddressMap["mac"]; ok {
				logicalPortAddress.MAC = mac.(string)
			}
			logicalPortsAddressesArr = append(logicalPortsAddressesArr, logicalPortAddress)
		}
		req.LogicalPortAddresses = logicalPortsAddressesArr
	}

	logicalPort, err := c.SDN().LogicalPorts().Create(ctx, req)
	if err != nil {
		d.SetId("")
		return diag.FromErr(err)
	}

	d.SetId(logicalPort.ID)

	return resourceLogicalPortRead(ctx, d, m)
}

func resourceLogicalPortRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
	log.Debugf("resourceLogicalPortRead: called logical port with id %s", d.Id())

	logicalPort, err := utilityLogicalPortCheckPresence(ctx, d, m)
	if err != nil {
		d.SetId("")
		return diag.FromErr(err)
	}

	flattenLogicalPortResource(d, logicalPort)
	d.SetId(logicalPort.ID)

	return nil
}

func resourceLogicalPortUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
	log.Debugf("resourceLogicalPortUpdate: called logical port with id %s", d.Id())
	c := m.(*controller.ControllerCfg)

	migrate := d.Get("migrate").(bool)

	req := logicalports.UpdateRequest{
		LogicalPortID: d.Id(),
		VersionID:     uint64(d.Get("version_id").(int)),
		AdapterMAC:    d.Get("adapter_mac").(string),
		Description:   d.Get("description").(string),
		DisplayName:   d.Get("display_name").(string),
		Enabled:       d.Get("enabled").(bool),
		PortSecurity:  d.Get("port_security").(bool),
		SegmentID:     d.Get("segment_id").(string),
	}

	if !migrate {
		req.Hypervisor = d.Get("hypervisor").(string)
	} else {
		old, _ := d.GetChange("hypervisor")
		req.Hypervisor = old.(string)
	}

	if d.HasChange("labels") {
		if labelsRaw, ok := d.GetOk("labels"); ok {
			labelsList := labelsRaw.([]interface{})
			if len(labelsList) > 0 {
				labelsMap := labelsList[0].(map[string]interface{})
				req.Labels = logicalports.UpdateLabels{
					VMID:   labelsMap["vm_id"].(string),
					VMName: labelsMap["vm_name"].(string),
				}
			}
		}
	}

	if d.HasChange("logical_port_addresses") {
		oldAddresses, newAddresses := d.GetChange("logical_port_addresses")
		oldAddressesList := oldAddresses.([]interface{})
		newAddressesList := newAddresses.([]interface{})
		oldAddressesMap := make(map[string]logicalports.LogicalPortAddress)
		for _, oldAddressRaw := range oldAddressesList {
			logicalPortAddressMap := oldAddressRaw.(map[string]interface{})
			logicalPortAddress := logicalports.LogicalPortAddress{
				IP:        logicalPortAddressMap["ip"].(string),
				IPType:    logicalPortAddressMap["ip_type"].(string),
				IsPrimary: logicalPortAddressMap["is_primary"].(bool),
			}
			if isDiscovered, ok := logicalPortAddressMap["is_discovered"]; ok {
				logicalPortAddress.IsDiscovered = isDiscovered.(bool)
			}
			if mac, ok := logicalPortAddressMap["mac"]; ok {
				logicalPortAddress.MAC = mac.(string)
			}
			oldAddressesMap[logicalPortAddress.IP] = logicalPortAddress
		}
		newAddressesMap := make(map[string]logicalports.LogicalPortAddress)
		for _, newAddressRaw := range newAddressesList {
			logicalPortAddressMap := newAddressRaw.(map[string]interface{})
			logicalPortAddress := logicalports.LogicalPortAddress{
				IP:        logicalPortAddressMap["ip"].(string),
				IPType:    logicalPortAddressMap["ip_type"].(string),
				IsPrimary: logicalPortAddressMap["is_primary"].(bool),
			}
			if isDiscovered, ok := logicalPortAddressMap["is_discovered"]; ok {
				logicalPortAddress.IsDiscovered = isDiscovered.(bool)
			}
			if mac, ok := logicalPortAddressMap["mac"]; ok {
				logicalPortAddress.MAC = mac.(string)
			}
			newAddressesMap[logicalPortAddress.IP] = logicalPortAddress
		}

		removeAddresses := make([]string, 0)
		for addressIP := range oldAddressesMap {
			if _, exists := newAddressesMap[addressIP]; !exists {
				removeAddresses = append(removeAddresses, addressIP)
			}
		}
		req.RemoveAddresses = removeAddresses

		for addressIP, address := range newAddressesMap {
			if oldAddressIP, exists := oldAddressesMap[addressIP]; !exists || address != oldAddressIP {
				if !exists {
					logicalPortAddress := logicalports.AddAddress{
						IP:           address.IP,
						IPType:       address.IPType,
						IsPrimary:    address.IsPrimary,
						IsDiscovered: address.IsDiscovered,
						MAC:          address.MAC,
					}
					req.AddAddresses = append(req.AddAddresses, logicalPortAddress)
				} else if address != oldAddressIP {
					logicalPortAddress := logicalports.UpdateAddress{
						IP:           address.IP,
						IPType:       address.IPType,
						IsPrimary:    address.IsPrimary,
						IsDiscovered: address.IsDiscovered,
						MAC:          address.MAC,
					}
					req.UpdateAddresses = append(req.UpdateAddresses, logicalPortAddress)
				}
			}
		}

	}

	_, err := c.SDN().LogicalPorts().Update(ctx, req)
	if err != nil {
		d.SetId("")
		return diag.FromErr(err)
	}

	if migrate {
		if targetHV, ok := d.GetOk("hypervisor"); ok {
			// Re-read version_id
			lp, err := utilityLogicalPortCheckPresence(ctx, d, m)
			if err != nil {
				return diag.FromErr(err)
			}

			migrateReq := logicalports.MigrateStartRequest{
				LogicalPortID:    d.Id(),
				VersionID:        lp.VersionID,
				TargetHypervisor: targetHV.(string),
			}

			_, err = c.SDN().LogicalPorts().StartMigrate(ctx, migrateReq)
			if err != nil {
				return diag.FromErr(err)
			}
			// to prevent drift on the next plan
			diags := resourceLogicalPortRead(ctx, d, m)
			if diags.HasError() {
				return diags
			}
			d.Set("hypervisor", targetHV.(string))
			return diags
		}
	}

	return resourceLogicalPortRead(ctx, d, m)
}

func resourceLogicalPortDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
	log.Debugf("resourceLogicalPortDelete: called logical port with id %s", d.Id())
	c := m.(*controller.ControllerCfg)

	req := logicalports.DeleteRequest{
		ID:      d.Id(),
		Version: uint64(d.Get("version_id").(int)),
	}

	if force, ok := d.GetOk("force"); ok {
		req.Force = force.(bool)
	}

	_, err := c.SDN().LogicalPorts().Delete(ctx, req)
	if err != nil {
		return diag.FromErr(err)
	}

	d.SetId("")

	return nil
}

func ResourceLogicalPort() *schema.Resource {
	return &schema.Resource{
		SchemaVersion: 1,

		CreateContext: resourceLogicalPortCreate,
		ReadContext:   resourceLogicalPortRead,
		UpdateContext: resourceLogicalPortUpdate,
		DeleteContext: resourceLogicalPortDelete,

		Importer: &schema.ResourceImporter{
			StateContext: schema.ImportStatePassthroughContext,
		},

		Timeouts: &schema.ResourceTimeout{
			Create:  &constants.Timeout600s,
			Read:    &constants.Timeout300s,
			Update:  &constants.Timeout600s,
			Delete:  &constants.Timeout300s,
			Default: &constants.Timeout300s,
		},

		Schema: resourceLogicalPortSchemaMake(),
	}
}
