package common

import (
	"context"
	"fmt"
	"log"
	"strings"

	"github.com/antihax/optional"
	"github.com/hashicorp/packer/packer-plugin-sdk/communicator"
	"github.com/hashicorp/packer/packer-plugin-sdk/multistep"
	packersdk "github.com/hashicorp/packer/packer-plugin-sdk/packer"
	"github.com/hashicorp/packer/packer-plugin-sdk/uuid"
	"github.com/outscale/osc-sdk-go/osc"
)

type StepSecurityGroup struct {
	CommConfig            *communicator.Config
	SecurityGroupFilter   SecurityGroupFilterOptions
	SecurityGroupIds      []string
	TemporarySGSourceCidr string

	createdGroupId string
}

func (s *StepSecurityGroup) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
	var (
		ui    = state.Get("ui").(packersdk.Ui)
		conn  = state.Get("osc").(*osc.APIClient)
		netID = state.Get("net_id").(string)
	)

	if len(s.SecurityGroupIds) > 0 {
		resp, _, err := conn.SecurityGroupApi.ReadSecurityGroups(context.Background(), &osc.ReadSecurityGroupsOpts{
			ReadSecurityGroupsRequest: optional.NewInterface(osc.ReadSecurityGroupsRequest{
				Filters: osc.FiltersSecurityGroup{
					SecurityGroupIds: s.SecurityGroupIds,
				},
			}),
		})

		if err != nil || len(resp.SecurityGroups) == 0 {
			err := fmt.Errorf("Couldn't find specified security group: %s", err)
			log.Printf("[DEBUG] %s", err.Error())
			state.Put("error", err)

			return multistep.ActionHalt
		}

		log.Printf("Using specified security groups: %v", s.SecurityGroupIds)
		state.Put("securityGroupIds", s.SecurityGroupIds)

		return multistep.ActionContinue
	}

	if !s.SecurityGroupFilter.Empty() {
		filterReq := buildSecurityGroupFilters(s.SecurityGroupFilter.Filters)

		log.Printf("Using SecurityGroup Filters %v", filterReq)

		resp, _, err := conn.SecurityGroupApi.ReadSecurityGroups(context.Background(), &osc.ReadSecurityGroupsOpts{
			ReadSecurityGroupsRequest: optional.NewInterface(osc.ReadSecurityGroupsRequest{
				Filters: filterReq,
			}),
		})

		if err != nil || len(resp.SecurityGroups) == 0 {
			err := fmt.Errorf("Couldn't find security groups for filter: %s", err)
			log.Printf("[DEBUG] %s", err.Error())
			state.Put("error", err)

			return multistep.ActionHalt
		}

		securityGroupIds := []string{}
		for _, sg := range resp.SecurityGroups {
			securityGroupIds = append(securityGroupIds, sg.SecurityGroupId)
		}

		ui.Message(fmt.Sprintf("Found Security Group(s): %s", strings.Join(securityGroupIds, ", ")))
		state.Put("securityGroupIds", securityGroupIds)

		return multistep.ActionContinue
	}

	/* Create the group */
	groupName := fmt.Sprintf("packer_osc_%s", uuid.TimeOrderedUUID())
	ui.Say(fmt.Sprintf("Creating temporary security group for this instance: %s", groupName))

	createSGReq := osc.CreateSecurityGroupRequest{
		SecurityGroupName: groupName,
		NetId:             netID,
		Description:       "Temporary group for Packer",
	}

	resp, _, err := conn.SecurityGroupApi.CreateSecurityGroup(context.Background(), &osc.CreateSecurityGroupOpts{
		CreateSecurityGroupRequest: optional.NewInterface(createSGReq),
	})

	if err != nil {
		ui.Error(err.Error())
		state.Put("error", err)
		return multistep.ActionHalt
	}

	// Set the group ID so we can delete it later
	s.createdGroupId = resp.SecurityGroup.SecurityGroupId

	port := s.CommConfig.Port()
	if port == 0 {
		if s.CommConfig.Type != "none" {
			state.Put("error", "port must be set to a non-zero value.")
			return multistep.ActionHalt
		}
	}

	// Authorize the SSH access for the security group
	createSGRReq := osc.CreateSecurityGroupRuleRequest{
		SecurityGroupId: resp.SecurityGroup.SecurityGroupId,
		Flow:            "Inbound",
		Rules: []osc.SecurityGroupRule{
			{
				FromPortRange: int32(port),
				ToPortRange:   int32(port),
				IpRanges:      []string{s.TemporarySGSourceCidr},
				IpProtocol:    "tcp",
			},
		},
	}

	ui.Say(fmt.Sprintf("Authorizing access to port %d from %s in the temporary security group...", port, s.TemporarySGSourceCidr))

	_, _, err = conn.SecurityGroupRuleApi.CreateSecurityGroupRule(context.Background(), &osc.CreateSecurityGroupRuleOpts{
		CreateSecurityGroupRuleRequest: optional.NewInterface(createSGRReq),
	})

	if err != nil {
		err := fmt.Errorf("Error authorizing temporary security group: %s", err)
		state.Put("error", err)
		ui.Error(err.Error())
		return multistep.ActionHalt
	}

	// Set some state data for use in future steps
	state.Put("securityGroupIds", []string{s.createdGroupId})

	return multistep.ActionContinue
}

func (s *StepSecurityGroup) Cleanup(state multistep.StateBag) {
	if s.createdGroupId == "" {
		return
	}

	var (
		ui   = state.Get("ui").(packersdk.Ui)
		conn = state.Get("osc").(*osc.APIClient)
	)

	ui.Say("Deleting temporary security group...")

	_, _, err := conn.SecurityGroupApi.DeleteSecurityGroup(context.Background(), &osc.DeleteSecurityGroupOpts{
		DeleteSecurityGroupRequest: optional.NewInterface(osc.DeleteSecurityGroupRequest{
			SecurityGroupId: s.createdGroupId,
		}),
	})

	if err != nil {
		ui.Error(fmt.Sprintf(
			"Error cleaning up security group. Please delete the group manually: %s", s.createdGroupId))
	}
}

func buildSecurityGroupFilters(input map[string]string) osc.FiltersSecurityGroup {
	var filters osc.FiltersSecurityGroup

	for k, v := range input {
		filterValue := []string{v}

		switch name := k; name {
		case "account_ids":
			filters.AccountIds = filterValue
		case "security_group_ids":
			filters.SecurityGroupIds = filterValue
		case "security_group_names":
			filters.SecurityGroupNames = filterValue
		case "tag_keys":
			filters.TagKeys = filterValue
		case "tag_values":
			filters.TagValues = filterValue
		case "tags":
			filters.Tags = filterValue
		default:
			log.Printf("[Debug] Unknown Filter Name: %s.", name)
		}
	}

	return filters
}
