package ecs

import (
	"context"
	"errors"
	"fmt"
	"time"

	"github.com/denverdino/aliyungo/common"
	"github.com/denverdino/aliyungo/ecs"
	"github.com/hashicorp/packer/helper/multistep"
	"github.com/hashicorp/packer/packer"
)

type stepConfigAlicloudSecurityGroup struct {
	SecurityGroupId   string
	SecurityGroupName string
	Description       string
	VpcId             string
	RegionId          string
	isCreate          bool
}

func (s *stepConfigAlicloudSecurityGroup) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
	client := state.Get("client").(*ecs.Client)
	ui := state.Get("ui").(packer.Ui)
	networkType := state.Get("networktype").(InstanceNetWork)

	var securityGroupItems []ecs.SecurityGroupItemType
	var err error
	if len(s.SecurityGroupId) != 0 {
		if networkType == VpcNet {
			vpcId := state.Get("vpcid").(string)
			securityGroupItems, _, err = client.DescribeSecurityGroups(&ecs.DescribeSecurityGroupsArgs{
				VpcId:    vpcId,
				RegionId: common.Region(s.RegionId),
			})
		} else {
			securityGroupItems, _, err = client.DescribeSecurityGroups(&ecs.DescribeSecurityGroupsArgs{
				RegionId: common.Region(s.RegionId),
			})
		}

		if err != nil {
			ui.Say(fmt.Sprintf("Failed querying security group: %s", err))
			state.Put("error", err)
			return multistep.ActionHalt
		}
		for _, securityGroupItem := range securityGroupItems {
			if securityGroupItem.SecurityGroupId == s.SecurityGroupId {
				state.Put("securitygroupid", s.SecurityGroupId)
				s.isCreate = false
				return multistep.ActionContinue
			}
		}
		s.isCreate = false
		message := fmt.Sprintf("The specified security group {%s} doesn't exist.", s.SecurityGroupId)
		state.Put("error", errors.New(message))
		ui.Say(message)
		return multistep.ActionHalt

	}
	var securityGroupId string
	ui.Say("Creating security groups...")
	if networkType == VpcNet {
		vpcId := state.Get("vpcid").(string)
		securityGroupId, err = client.CreateSecurityGroup(&ecs.CreateSecurityGroupArgs{
			RegionId:          common.Region(s.RegionId),
			SecurityGroupName: s.SecurityGroupName,
			VpcId:             vpcId,
		})
	} else {
		securityGroupId, err = client.CreateSecurityGroup(&ecs.CreateSecurityGroupArgs{
			RegionId:          common.Region(s.RegionId),
			SecurityGroupName: s.SecurityGroupName,
		})
	}
	if err != nil {
		state.Put("error", err)
		ui.Say(fmt.Sprintf("Failed creating security group %s.", err))
		return multistep.ActionHalt
	}
	state.Put("securitygroupid", securityGroupId)
	s.isCreate = true
	s.SecurityGroupId = securityGroupId
	err = client.AuthorizeSecurityGroupEgress(&ecs.AuthorizeSecurityGroupEgressArgs{
		SecurityGroupId: securityGroupId,
		RegionId:        common.Region(s.RegionId),
		IpProtocol:      ecs.IpProtocolAll,
		PortRange:       "-1/-1",
		NicType:         ecs.NicTypeInternet,
		DestCidrIp:      "0.0.0.0/0", //The input parameter "DestGroupId" or "DestCidrIp" cannot be both blank.
	})
	if err != nil {
		state.Put("error", err)
		ui.Say(fmt.Sprintf("Failed authorizing security group: %s", err))
		return multistep.ActionHalt
	}
	err = client.AuthorizeSecurityGroup(&ecs.AuthorizeSecurityGroupArgs{
		SecurityGroupId: securityGroupId,
		RegionId:        common.Region(s.RegionId),
		IpProtocol:      ecs.IpProtocolAll,
		PortRange:       "-1/-1",
		NicType:         ecs.NicTypeInternet,
		SourceCidrIp:    "0.0.0.0/0", //The input parameter "SourceGroupId" or "SourceCidrIp" cannot be both blank.
	})
	if err != nil {
		state.Put("error", err)
		ui.Say(fmt.Sprintf("Failed authorizing security group: %s", err))
		return multistep.ActionHalt
	}

	return multistep.ActionContinue
}

func (s *stepConfigAlicloudSecurityGroup) Cleanup(state multistep.StateBag) {
	if !s.isCreate {
		return
	}

	client := state.Get("client").(*ecs.Client)
	ui := state.Get("ui").(packer.Ui)

	message(state, "security group")
	timeoutPoint := time.Now().Add(120 * time.Second)
	for {
		if err := client.DeleteSecurityGroup(common.Region(s.RegionId), s.SecurityGroupId); err != nil {
			e, _ := err.(*common.Error)
			if e.Code == "DependencyViolation" && time.Now().Before(timeoutPoint) {
				time.Sleep(5 * time.Second)
				continue
			}
			ui.Error(fmt.Sprintf("Failed to delete security group, it may still be around: %s", err))
			return
		}
		break
	}
}
