package main

import (
	"context"
	"errors"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"strings"

	hversion "github.com/hashicorp/go-version" //nolint:gomodguard // hc-install uses hashicorp/go-version
	"github.com/hashicorp/hc-install/product"
	"github.com/hashicorp/hc-install/releases"
	"github.com/hashicorp/terraform-exec/tfexec"
	"golang.org/x/sync/errgroup"

	"github.com/aquasecurity/trivy/internal/testutil"
)

const (
	terraformVersion       = "1.7.3"
	terraformParallelLimit = 5

	tfplanFile = "tfplan"
)

func fixtureTerraformPlanSnapshots(ctx context.Context) error {
	localstackC, addr, err := testutil.SetupLocalStack(ctx, "3.1.0")
	if err != nil {
		return err
	}
	defer localstackC.Terminate(ctx)

	envs := []struct {
		key string
		val string
	}{
		{"AWS_DEFAULT_REGION", "us-east-1"},
		{"AWS_ACCESS_KEY_ID", "test"},
		{"AWS_SECRET_ACCESS_KEY", "test"},
		{"AWS_ENDPOINT_URL", addr},
	}

	for _, env := range envs {
		if err := os.Setenv(env.key, env.val); err != nil {
			return err
		}
	}

	dirs := []string{
		"pkg/fanal/artifact/local/testdata/misconfig/terraformplan/snapshots",
		"pkg/iac/scanners/terraformplan/snapshot/testdata",
	}

	var workingDirs []string

	for _, dir := range dirs {
		entries, err := os.ReadDir(filepath.FromSlash(dir))
		if err != nil {
			return err
		}

		for _, entry := range entries {
			workingDirs = append(workingDirs, filepath.Join(dir, entry.Name()))
		}
	}

	installer := &releases.ExactVersion{
		Product: product.Terraform,
		Version: hversion.Must(hversion.NewVersion(terraformVersion)),
	}

	execPath, err := installer.Install(ctx)
	if err != nil {
		return fmt.Errorf("failed to install Terraform: %w", err)
	}

	g, ctx := errgroup.WithContext(ctx)
	g.SetLimit(terraformParallelLimit)

	for _, workingDir := range workingDirs {
		g.Go(func() error {
			if err := os.Remove(tfplanFile); err != nil && !errors.Is(err, os.ErrNotExist) {
				return err
			}

			if err := generatePlan(ctx, execPath, workingDir); err != nil {
				return fmt.Errorf("failed to generate Terraform Plan: %w", err)
			}

			return nil
		})
	}

	return g.Wait()
}

func generatePlan(ctx context.Context, execPath, workingDir string) error {
	if err := cleanup(workingDir); err != nil {
		return err
	}
	defer cleanup(workingDir)

	tf, err := tfexec.NewTerraform(workingDir, execPath)
	if err != nil {
		return fmt.Errorf("failed to run Terraform: %w", err)
	}

	prefix := fmt.Sprintf("tfplan:%s:", filepath.Base(workingDir))
	tf.SetLogger(log.New(os.Stdout, prefix, log.LstdFlags))

	if err = tf.Init(ctx, tfexec.Upgrade(true)); err != nil {
		return fmt.Errorf("failed to run Init cmd: %w", err)
	}

	if _, err := tf.Plan(ctx, tfexec.Out(tfplanFile)); err != nil {
		return fmt.Errorf("failed to run Plan cmd: %w", err)
	}

	return nil
}

func cleanup(workingDir string) error {
	entries, err := os.ReadDir(workingDir)
	if err != nil {
		return err
	}

	for _, entry := range entries {
		if entry.Name() == "terraform.tfstate" || strings.HasPrefix(entry.Name(), ".terraform") {
			path := filepath.Join(workingDir, entry.Name())
			if err := os.RemoveAll(path); err != nil && !errors.Is(err, os.ErrNotExist) {
				return err
			}
		}
	}
	return nil
}
