package aws

import (
	"fmt"
	"log"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/ec2"
	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func resourceAwsNetworkInterfaceAttachment() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsNetworkInterfaceAttachmentCreate,
		Read:   resourceAwsNetworkInterfaceAttachmentRead,
		Delete: resourceAwsNetworkInterfaceAttachmentDelete,

		Schema: map[string]*schema.Schema{
			"device_index": {
				Type:     schema.TypeInt,
				Required: true,
				ForceNew: true,
			},

			"instance_id": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"network_interface_id": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"attachment_id": {
				Type:     schema.TypeString,
				Computed: true,
			},

			"status": {
				Type:     schema.TypeString,
				Computed: true,
			},
		},
	}
}

func resourceAwsNetworkInterfaceAttachmentCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ec2conn

	device_index := d.Get("device_index").(int)
	instance_id := d.Get("instance_id").(string)
	network_interface_id := d.Get("network_interface_id").(string)

	opts := &ec2.AttachNetworkInterfaceInput{
		DeviceIndex:        aws.Int64(int64(device_index)),
		InstanceId:         aws.String(instance_id),
		NetworkInterfaceId: aws.String(network_interface_id),
	}

	log.Printf("[DEBUG] Attaching network interface (%s) to instance (%s)", network_interface_id, instance_id)
	resp, err := conn.AttachNetworkInterface(opts)
	if err != nil {
		if awsErr, ok := err.(awserr.Error); ok {
			return fmt.Errorf("Error attaching network interface (%s) to instance (%s), message: \"%s\", code: \"%s\"",
				network_interface_id, instance_id, awsErr.Message(), awsErr.Code())
		}
		return err
	}

	stateConf := &resource.StateChangeConf{
		Pending:    []string{"false"},
		Target:     []string{"true"},
		Refresh:    networkInterfaceAttachmentRefreshFunc(conn, network_interface_id),
		Timeout:    5 * time.Minute,
		Delay:      10 * time.Second,
		MinTimeout: 3 * time.Second,
	}

	_, err = stateConf.WaitForState()
	if err != nil {
		return fmt.Errorf(
			"Error waiting for Volume (%s) to attach to Instance: %s, error: %s", network_interface_id, instance_id, err)
	}

	d.SetId(*resp.AttachmentId)
	return resourceAwsNetworkInterfaceAttachmentRead(d, meta)
}

func resourceAwsNetworkInterfaceAttachmentRead(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ec2conn

	interfaceId := d.Get("network_interface_id").(string)

	req := &ec2.DescribeNetworkInterfacesInput{
		NetworkInterfaceIds: []*string{aws.String(interfaceId)},
	}

	resp, err := conn.DescribeNetworkInterfaces(req)
	if err != nil {
		if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidNetworkInterfaceID.NotFound" {
			// The ENI is gone now, so just remove the attachment from the state
			d.SetId("")
			return nil
		}

		return fmt.Errorf("Error retrieving ENI: %s", err)
	}
	if len(resp.NetworkInterfaces) != 1 {
		return fmt.Errorf("Unable to find ENI (%s): %#v", interfaceId, resp.NetworkInterfaces)
	}

	eni := resp.NetworkInterfaces[0]

	if eni.Attachment == nil {
		// Interface is no longer attached, remove from state
		d.SetId("")
		return nil
	}

	d.Set("attachment_id", eni.Attachment.AttachmentId)
	d.Set("device_index", eni.Attachment.DeviceIndex)
	d.Set("instance_id", eni.Attachment.InstanceId)
	d.Set("network_interface_id", eni.NetworkInterfaceId)
	d.Set("status", eni.Attachment.Status)

	return nil
}

func resourceAwsNetworkInterfaceAttachmentDelete(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ec2conn

	interfaceId := d.Get("network_interface_id").(string)

	detach_request := &ec2.DetachNetworkInterfaceInput{
		AttachmentId: aws.String(d.Id()),
		Force:        aws.Bool(true),
	}

	_, detach_err := conn.DetachNetworkInterface(detach_request)
	if detach_err != nil {
		if awsErr, _ := detach_err.(awserr.Error); awsErr.Code() != "InvalidAttachmentID.NotFound" {
			return fmt.Errorf("Error detaching ENI: %s", detach_err)
		}
	}

	log.Printf("[DEBUG] Waiting for ENI (%s) to become detached", interfaceId)
	stateConf := &resource.StateChangeConf{
		Pending: []string{"true"},
		Target:  []string{"false"},
		Refresh: networkInterfaceAttachmentRefreshFunc(conn, interfaceId),
		Timeout: 10 * time.Minute,
	}

	if _, err := stateConf.WaitForState(); err != nil {
		return fmt.Errorf(
			"Error waiting for ENI (%s) to become detached: %s", interfaceId, err)
	}

	return nil
}
