package linodego

import (
	"context"
)

// InstanceConfigInterface contains information about a configuration's network interface
type InstanceConfigInterface struct {
	ID          int                    `json:"id"`
	IPAMAddress string                 `json:"ipam_address"`
	Label       string                 `json:"label"`
	Purpose     ConfigInterfacePurpose `json:"purpose"`
	Primary     bool                   `json:"primary"`
	Active      bool                   `json:"active"`
	VPCID       *int                   `json:"vpc_id"`
	SubnetID    *int                   `json:"subnet_id"`
	IPv4        *VPCIPv4               `json:"ipv4"`

	// NOTE: IPv6 interfaces may not currently be available to all users.
	IPv6 *InstanceConfigInterfaceIPv6 `json:"ipv6"`

	IPRanges []string `json:"ip_ranges"`
}

// InstanceConfigInterfaceIPv6 represents the IPv6 configuration of a Linode interface.
// NOTE: IPv6 interfaces may not currently be available to all users.
type InstanceConfigInterfaceIPv6 struct {
	SLAAC    []InstanceConfigInterfaceIPv6SLAAC `json:"slaac"`
	Ranges   []InstanceConfigInterfaceIPv6Range `json:"ranges"`
	IsPublic *bool                              `json:"is_public"`
}

// InstanceConfigInterfaceIPv6SLAAC represents a single IPv6 SLAAC under
// a Linode interface.
// NOTE: IPv6 interfaces may not currently be available to all users.
type InstanceConfigInterfaceIPv6SLAAC struct {
	Range   string `json:"range"`
	Address string `json:"address"`
}

// InstanceConfigInterfaceIPv6Range represents a single IPv6 range under a Linode interface.
// NOTE: IPv6 interfaces may not currently be available to all users.
type InstanceConfigInterfaceIPv6Range struct {
	Range string `json:"range"`
}

type VPCIPv4 struct {
	VPC     string  `json:"vpc,omitempty"`
	NAT1To1 *string `json:"nat_1_1,omitempty"`
}

type InstanceConfigInterfaceCreateOptions struct {
	IPAMAddress string                 `json:"ipam_address,omitempty"`
	Label       string                 `json:"label,omitempty"`
	Purpose     ConfigInterfacePurpose `json:"purpose,omitempty"`
	Primary     bool                   `json:"primary,omitempty"`
	SubnetID    *int                   `json:"subnet_id,omitempty"`
	IPv4        *VPCIPv4               `json:"ipv4,omitempty"`

	// NOTE: IPv6 interfaces may not currently be available to all users.
	IPv6 *InstanceConfigInterfaceCreateOptionsIPv6 `json:"ipv6,omitempty"`

	IPRanges []string `json:"ip_ranges,omitempty"`
}

// InstanceConfigInterfaceCreateOptionsIPv6 represents the IPv6 configuration of a Linode interface
// specified during creation.
// NOTE: IPv6 interfaces may not currently be available to all users.
type InstanceConfigInterfaceCreateOptionsIPv6 struct {
	SLAAC    []InstanceConfigInterfaceCreateOptionsIPv6SLAAC `json:"slaac,omitempty"`
	Ranges   []InstanceConfigInterfaceCreateOptionsIPv6Range `json:"ranges,omitempty"`
	IsPublic *bool                                           `json:"is_public,omitempty"`
}

// InstanceConfigInterfaceCreateOptionsIPv6SLAAC represents a single IPv6 SLAAC of a Linode interface
// specified during creation.
// NOTE: IPv6 interfaces may not currently be available to all users.
type InstanceConfigInterfaceCreateOptionsIPv6SLAAC struct {
	Range string `json:"range"`
}

// InstanceConfigInterfaceCreateOptionsIPv6Range represents a single IPv6 ranges of a Linode interface
// specified during creation.
// NOTE: IPv6 interfaces may not currently be available to all users.
type InstanceConfigInterfaceCreateOptionsIPv6Range struct {
	Range *string `json:"range,omitempty"`
}

type InstanceConfigInterfaceUpdateOptions struct {
	Primary bool     `json:"primary,omitempty"`
	IPv4    *VPCIPv4 `json:"ipv4,omitempty"`

	// NOTE: IPv6 interfaces may not currently be available to all users.
	IPv6 *InstanceConfigInterfaceUpdateOptionsIPv6 `json:"ipv6,omitempty"`

	IPRanges *[]string `json:"ip_ranges,omitempty"`
}

// InstanceConfigInterfaceUpdateOptionsIPv6 represents the IPv6 configuration of a Linode interface
// specified during updates.
// NOTE: IPv6 interfaces may not currently be available to all users.
type InstanceConfigInterfaceUpdateOptionsIPv6 struct {
	SLAAC    *[]InstanceConfigInterfaceUpdateOptionsIPv6SLAAC `json:"slaac,omitempty"`
	Ranges   *[]InstanceConfigInterfaceUpdateOptionsIPv6Range `json:"ranges,omitempty"`
	IsPublic *bool                                            `json:"is_public,omitempty"`
}

// InstanceConfigInterfaceUpdateOptionsIPv6SLAAC represents a single IPv6 SLAAC of a Linode interface
// specified during updates.
// NOTE: IPv6 interfaces may not currently be available to all users.
type InstanceConfigInterfaceUpdateOptionsIPv6SLAAC struct {
	Range *string `json:"range,omitempty"`
}

// InstanceConfigInterfaceUpdateOptionsIPv6Range represents a single IPv6 ranges of a Linode interface
// specified during updates.
// NOTE: IPv6 interfaces may not currently be available to all users.
type InstanceConfigInterfaceUpdateOptionsIPv6Range struct {
	Range *string `json:"range,omitempty"`
}

type InstanceConfigInterfacesReorderOptions struct {
	IDs []int `json:"ids"`
}

func getInstanceConfigInterfacesCreateOptionsList(
	interfaces []InstanceConfigInterface,
) []InstanceConfigInterfaceCreateOptions {
	interfaceOptsList := make([]InstanceConfigInterfaceCreateOptions, len(interfaces))
	for index, configInterface := range interfaces {
		interfaceOptsList[index] = configInterface.GetCreateOptions()
	}

	return interfaceOptsList
}

func (i InstanceConfigInterface) GetCreateOptions() InstanceConfigInterfaceCreateOptions {
	opts := InstanceConfigInterfaceCreateOptions{
		Label:    i.Label,
		Purpose:  i.Purpose,
		Primary:  i.Primary,
		SubnetID: i.SubnetID,
	}

	if len(i.IPRanges) > 0 {
		opts.IPRanges = i.IPRanges
	}

	if i.IPv4 != nil {
		opts.IPv4 = &VPCIPv4{
			VPC:     i.IPv4.VPC,
			NAT1To1: i.IPv4.NAT1To1,
		}
	}

	if i.IPv6 != nil {
		ipv6 := *i.IPv6

		opts.IPv6 = &InstanceConfigInterfaceCreateOptionsIPv6{
			SLAAC: mapSlice(
				ipv6.SLAAC,
				func(i InstanceConfigInterfaceIPv6SLAAC) InstanceConfigInterfaceCreateOptionsIPv6SLAAC {
					return InstanceConfigInterfaceCreateOptionsIPv6SLAAC{
						Range: i.Range,
					}
				},
			),
			Ranges: mapSlice(
				ipv6.Ranges,
				func(i InstanceConfigInterfaceIPv6Range) InstanceConfigInterfaceCreateOptionsIPv6Range {
					return InstanceConfigInterfaceCreateOptionsIPv6Range{
						Range: copyValue(&i.Range),
					}
				},
			),
			IsPublic: copyValue(ipv6.IsPublic),
		}
	}

	opts.IPAMAddress = i.IPAMAddress

	return opts
}

func (i InstanceConfigInterface) GetUpdateOptions() InstanceConfigInterfaceUpdateOptions {
	opts := InstanceConfigInterfaceUpdateOptions{
		Primary: i.Primary,
	}

	if i.Purpose == InterfacePurposeVPC {
		if i.IPv4 != nil {
			opts.IPv4 = &VPCIPv4{
				VPC:     i.IPv4.VPC,
				NAT1To1: i.IPv4.NAT1To1,
			}
		}

		if i.IPv6 != nil {
			ipv6 := *i.IPv6

			newSLAAC := mapSlice(
				ipv6.SLAAC,
				func(i InstanceConfigInterfaceIPv6SLAAC) InstanceConfigInterfaceUpdateOptionsIPv6SLAAC {
					return InstanceConfigInterfaceUpdateOptionsIPv6SLAAC{
						Range: copyValue(&i.Range),
					}
				},
			)

			newRanges := mapSlice(
				ipv6.Ranges,
				func(i InstanceConfigInterfaceIPv6Range) InstanceConfigInterfaceUpdateOptionsIPv6Range {
					return InstanceConfigInterfaceUpdateOptionsIPv6Range{
						Range: copyValue(&i.Range),
					}
				},
			)

			opts.IPv6 = &InstanceConfigInterfaceUpdateOptionsIPv6{
				SLAAC:    &newSLAAC,
				Ranges:   &newRanges,
				IsPublic: copyValue(ipv6.IsPublic),
			}
		}
	}

	if i.IPRanges != nil {
		// Copy the slice to prevent accidental
		// mutations
		copiedIPRanges := make([]string, len(i.IPRanges))
		copy(copiedIPRanges, i.IPRanges)

		opts.IPRanges = &copiedIPRanges
	}

	return opts
}

func (c *Client) AppendInstanceConfigInterface(
	ctx context.Context,
	linodeID int,
	configID int,
	opts InstanceConfigInterfaceCreateOptions,
) (*InstanceConfigInterface, error) {
	e := formatAPIPath("/linode/instances/%d/configs/%d/interfaces", linodeID, configID)
	return doPOSTRequest[InstanceConfigInterface](ctx, c, e, opts)
}

func (c *Client) GetInstanceConfigInterface(
	ctx context.Context,
	linodeID int,
	configID int,
	interfaceID int,
) (*InstanceConfigInterface, error) {
	e := formatAPIPath(
		"linode/instances/%d/configs/%d/interfaces/%d",
		linodeID,
		configID,
		interfaceID,
	)

	return doGETRequest[InstanceConfigInterface](ctx, c, e)
}

func (c *Client) ListInstanceConfigInterfaces(
	ctx context.Context,
	linodeID int,
	configID int,
) ([]InstanceConfigInterface, error) {
	e := formatAPIPath(
		"linode/instances/%d/configs/%d/interfaces",
		linodeID,
		configID,
	)

	response, err := doGETRequest[[]InstanceConfigInterface](ctx, c, e)
	if err != nil {
		return nil, err
	}

	return *response, nil
}

func (c *Client) UpdateInstanceConfigInterface(
	ctx context.Context,
	linodeID int,
	configID int,
	interfaceID int,
	opts InstanceConfigInterfaceUpdateOptions,
) (*InstanceConfigInterface, error) {
	e := formatAPIPath(
		"linode/instances/%d/configs/%d/interfaces/%d",
		linodeID,
		configID,
		interfaceID,
	)

	return doPUTRequest[InstanceConfigInterface](ctx, c, e, opts)
}

func (c *Client) DeleteInstanceConfigInterface(
	ctx context.Context,
	linodeID int,
	configID int,
	interfaceID int,
) error {
	e := formatAPIPath(
		"linode/instances/%d/configs/%d/interfaces/%d",
		linodeID,
		configID,
		interfaceID,
	)

	return doDELETERequest(ctx, c, e)
}

func (c *Client) ReorderInstanceConfigInterfaces(
	ctx context.Context,
	linodeID int,
	configID int,
	opts InstanceConfigInterfacesReorderOptions,
) error {
	e := formatAPIPath(
		"linode/instances/%d/configs/%d/interfaces/order",
		linodeID,
		configID,
	)

	return doPOSTRequestNoResponseBody(ctx, c, e, opts)
}
