// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package securitycenter

import (
	"fmt"
	"log"
	"strings"
	"time"

	"github.com/Azure/azure-sdk-for-go/services/preview/security/mgmt/v3.0/security" // nolint: staticcheck
	"github.com/hashicorp/go-azure-helpers/resourcemanager/commonids"
	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
	"github.com/hashicorp/terraform-provider-azurerm/helpers/azure"
	"github.com/hashicorp/terraform-provider-azurerm/helpers/tf"
	"github.com/hashicorp/terraform-provider-azurerm/internal/clients"
	computeParse "github.com/hashicorp/terraform-provider-azurerm/internal/services/compute/parse"
	"github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk"
	"github.com/hashicorp/terraform-provider-azurerm/internal/timeouts"
	"github.com/hashicorp/terraform-provider-azurerm/utils"
)

const (
	virtualMachineProvider = "Microsoft.Compute"
	hybridMachineProvider  = "Microsoft.HybridCompute"
)

func resourceServerVulnerabilityAssessment() *pluginsdk.Resource {
	return &pluginsdk.Resource{
		Create: resourceServerVulnerabilityAssessmentCreate,
		Read:   resourceServerVulnerabilityAssessmentRead,
		Delete: resourceServerVulnerabilityAssessmentDelete,

		// TODO: replace this with an importer which validates the ID during import
		Importer: &schema.ResourceImporter{
			StateContext: schema.ImportStatePassthroughContext,
		},

		Timeouts: &pluginsdk.ResourceTimeout{
			Create: pluginsdk.DefaultTimeout(5 * time.Minute),
			Read:   pluginsdk.DefaultTimeout(5 * time.Minute),
			Delete: pluginsdk.DefaultTimeout(10 * time.Minute),
		},

		DeprecationMessage: `This resource has been deprecated in favour of the 'azurerm_security_center_server_vulnerability_assessment_virtual_machine' resource and will be removed in v4.0 of the AzureRM Provider`,

		Schema: map[string]*pluginsdk.Schema{
			"virtual_machine_id": {
				Type:         pluginsdk.TypeString,
				Optional:     true,
				ForceNew:     true,
				ValidateFunc: commonids.ValidateVirtualMachineID,
			},
			"hybrid_machine_id": {
				Type:         pluginsdk.TypeString,
				Optional:     true,
				ForceNew:     true,
				ValidateFunc: azure.ValidateResourceID,
			},
		},
	}
}

func resourceServerVulnerabilityAssessmentCreate(d *pluginsdk.ResourceData, meta interface{}) error {
	client := meta.(*clients.Client).SecurityCenter.ServerVulnerabilityAssessmentClient
	ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d)
	defer cancel()

	targetId, err := parseTargetId(d)
	if err != nil {
		return err
	}

	vulnerabilityAssessment, err := client.Get(ctx, targetId.ResourceGroup, targetId.Provider, targetId.ResourceType, targetId.Name)
	if err != nil {
		if !utils.ResponseWasNotFound(vulnerabilityAssessment.Response) {
			return fmt.Errorf("checking for presence of existing Advanced Threat Protection for %q: %+v", targetId.Id, err)
		}
	}

	if vulnerabilityAssessment.ID != nil && *vulnerabilityAssessment.ID != "" {
		return tf.ImportAsExistsError("azurerm_security_center_server_vulnerability_assessment", *vulnerabilityAssessment.ID)
	}

	vulnerabilityAssessment, err = client.CreateOrUpdate(ctx, targetId.ResourceGroup, targetId.Provider, targetId.ResourceType, targetId.Name)
	if err != nil {
		return fmt.Errorf("create Server Vulnerability Assessment for %q: %+v", targetId.Id, err)
	}

	if vulnerabilityAssessment.ID != nil {
		d.SetId(*vulnerabilityAssessment.ID)
	}

	// Take some time for resource to be visible after creating. Hence the retry logic
	readError := resourceServerVulnerabilityAssessmentRead(d, meta)
	for retry := 1; d.Id() == "" && retry < 11; retry++ {
		time.Sleep(time.Duration(retry) * time.Second)
		readError = resourceServerVulnerabilityAssessmentRead(d, meta)
	}
	return readError
}

func resourceServerVulnerabilityAssessmentRead(d *pluginsdk.ResourceData, meta interface{}) error {
	client := meta.(*clients.Client).SecurityCenter.ServerVulnerabilityAssessmentClient
	ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d)
	defer cancel()

	targetId, err := parseTargetId(d)
	if err != nil {
		return err
	}

	resp, err := client.Get(ctx, targetId.ResourceGroup, targetId.Provider, targetId.ResourceType, targetId.Name)
	if err != nil {
		if utils.ResponseWasNotFound(resp.Response) {
			log.Printf("Server Vulnerability Assessment was not found for %q: %+v", targetId.Id, err)
			d.SetId("")
			return nil
		}

		return fmt.Errorf("retrieving Server Vulnerability Assessment status for %q: %+v", targetId.Id, err)
	}

	// Update state
	d.SetId(*resp.ID)

	// Handle import
	switch {
	case targetId.Provider == virtualMachineProvider && d.Get("virtual_machine_id") == "":
		_ = d.Set("virtual_machine_id", targetId.Id)
	case targetId.Provider == hybridMachineProvider && d.Get("hybrid_machine_id") == "":
		_ = d.Set("hybrid_machine_id", targetId.Id)
	default:
	}

	return nil
}

func resourceServerVulnerabilityAssessmentDelete(d *pluginsdk.ResourceData, meta interface{}) error {
	client := meta.(*clients.Client).SecurityCenter.ServerVulnerabilityAssessmentClient
	ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d)
	defer cancel()

	targetId, err := parseTargetId(d)
	if err != nil {
		return err
	}

	// Can not delete if still in provisioning state. Wait for it to complete.
	for retry := 1; retry <= 20; retry++ {
		response, err := client.Get(ctx, targetId.ResourceGroup, targetId.Provider, targetId.ResourceType, targetId.Name)
		if err != nil || response.ProvisioningState != security.ProvisioningState1Provisioning {
			break
		}
		time.Sleep(time.Duration(retry) * time.Second)
	}

	future, err := client.Delete(ctx, targetId.ResourceGroup, targetId.Provider, targetId.ResourceType, targetId.Name)
	if err != nil {
		return fmt.Errorf("deleting Server Vulnerability Assessment %s: %v", targetId.Id, err)
	}
	if err := future.WaitForCompletionRef(ctx, client.Client); err != nil {
		return fmt.Errorf("waiting for deletion of Server Vulnerability Assessment %s: %v", targetId.Id, err)
	}
	return nil
}

type TargetId struct {
	Id             string
	SubscriptionId string
	ResourceGroup  string
	Provider       string
	ResourceType   string
	Name           string
}

func parseTargetId(d *pluginsdk.ResourceData) (*TargetId, error) {
	rawVirtualMachineId := d.Get("virtual_machine_id").(string)
	rawHybridMachineId := d.Get("hybrid_machine_id").(string)
	switch {
	case rawVirtualMachineId != "" && rawHybridMachineId == "":
		return parseVirtualMachineId(rawVirtualMachineId)

	case rawVirtualMachineId == "" && rawHybridMachineId != "":
		return parseHybridMachineId(rawHybridMachineId)

	case rawVirtualMachineId == "" && rawHybridMachineId == "" && d.Id() != "": // When the resource is imported
		// Id of the vulnerability assessment extends the targets id, so we can get the target id from the first part of the vulnerability assessment id
		parts := strings.Split(d.Id(), "/")
		if len(parts) < 9 { // Index 0 is empty string
			return nil, fmt.Errorf("can not import id: %q", d.Id())
		}
		switch parts[6] {
		case virtualMachineProvider:
			return parseVirtualMachineId(strings.Join(parts[0:9], "/"))
		case hybridMachineProvider:
			return parseHybridMachineId(strings.Join(parts[0:9], "/"))
		default:
			return nil, fmt.Errorf("can not import id: %q", d.Id())
		}

	default:
		return nil, fmt.Errorf("either `virtual_machine_id` or `hybrid_machine_id` must be specified")
	}
}

func parseVirtualMachineId(rawVirtualMachineId string) (*TargetId, error) {
	virtualMachineId, err := commonids.ParseVirtualMachineID(rawVirtualMachineId)
	if err != nil {
		return nil, err
	}
	return &TargetId{
		Id:             rawVirtualMachineId,
		SubscriptionId: virtualMachineId.SubscriptionId,
		ResourceGroup:  virtualMachineId.ResourceGroupName,
		Provider:       virtualMachineProvider,
		ResourceType:   "virtualMachines",
		Name:           virtualMachineId.VirtualMachineName,
	}, nil
}

func parseHybridMachineId(rawHybridMachineId string) (*TargetId, error) {
	hybridMachineId, err := computeParse.HybridMachineID(rawHybridMachineId)
	if err != nil {
		return nil, err
	}
	return &TargetId{
		Id:             rawHybridMachineId,
		SubscriptionId: hybridMachineId.SubscriptionId,
		ResourceGroup:  hybridMachineId.ResourceGroup,
		Provider:       hybridMachineProvider,
		ResourceType:   "machines",
		Name:           hybridMachineId.MachineName,
	}, nil
}
