package exec

import (
	"context"
	"fmt"

	"github.com/gruntwork-io/terragrunt/internal/clihelper"
	"github.com/gruntwork-io/terragrunt/internal/configbridge"
	"github.com/gruntwork-io/terragrunt/internal/prepare"
	"github.com/gruntwork-io/terragrunt/internal/report"
	"github.com/gruntwork-io/terragrunt/internal/runner/run"
	"github.com/gruntwork-io/terragrunt/internal/runner/runcfg"
	"github.com/gruntwork-io/terragrunt/internal/shell"
	"github.com/gruntwork-io/terragrunt/internal/venv"
	"github.com/gruntwork-io/terragrunt/pkg/log"
	"github.com/gruntwork-io/terragrunt/pkg/options"
)

func Run(
	ctx context.Context,
	l log.Logger,
	v venv.Venv,
	opts *options.TerragruntOptions,
	cmdOpts *Options,
	args clihelper.Args,
) error {
	prepared, err := prepare.PrepareConfig(ctx, l, v, opts)
	if err != nil {
		return err
	}

	r := report.NewReport()

	// Download source
	updatedOpts, err := prepare.PrepareSource(ctx, l, v, prepared.Opts, prepared.Cfg, r)
	if err != nil {
		return err
	}

	runCfg := prepared.Cfg.ToRunConfig(l)

	// Generate config
	if err := prepare.PrepareGenerate(l, v, updatedOpts, runCfg); err != nil {
		return err
	}

	if cmdOpts.InDownloadDir {
		// Run terraform init
		if err := prepare.PrepareInit(ctx, l, v, opts, updatedOpts, runCfg, r); err != nil {
			return err
		}
	} else {
		// Just set inputs as env vars, skip init
		updatedOpts.AutoInit = false

		if err := prepare.PrepareInputsAsEnvVars(l, v, updatedOpts, runCfg); err != nil {
			return err
		}
	}

	return runTargetCommand(ctx, l, v, updatedOpts, runCfg, r, cmdOpts, args)
}

func runTargetCommand(
	ctx context.Context,
	l log.Logger,
	v venv.Venv,
	opts *options.TerragruntOptions,
	cfg *runcfg.RunConfig,
	r *report.Report,
	cmdOpts *Options,
	args clihelper.Args,
) error {
	var (
		command = args.CommandName()
		cmdArgs = args.Tail()
		dir     = opts.WorkingDir
	)

	if !cmdOpts.InDownloadDir {
		dir = opts.RootWorkingDir
	}

	runOpts := configbridge.NewRunOptions(opts)

	return run.RunActionWithHooks(ctx, l, v, command, runOpts, cfg, r, func(ctx context.Context) error {
		_, err := shell.RunCommandWithOutput(
			ctx, l, v, configbridge.ShellRunOptsFromOpts(opts), dir, false, false, command, cmdArgs...,
		)
		if err != nil {
			return fmt.Errorf("failed to run command in directory %s: %w", dir, err)
		}

		return nil
	})
}
