// Copyright IBM Corp. 2020, 2026
// SPDX-License-Identifier: MPL-2.0

package tfexec

import (
	"context"
	"fmt"
	"io"
	"os/exec"
)

type initConfig struct {
	backend       bool
	backendConfig []string
	dir           string
	forceCopy     bool
	fromModule    string
	get           bool
	getPlugins    bool
	lock          bool
	lockTimeout   string
	pluginDir     []string
	reattachInfo  ReattachInfo
	reconfigure   bool
	upgrade       bool
	verifyPlugins bool
}

var defaultInitOptions = initConfig{
	backend:       true,
	forceCopy:     false,
	get:           true,
	getPlugins:    true,
	lock:          true,
	lockTimeout:   "0s",
	reconfigure:   false,
	upgrade:       false,
	verifyPlugins: true,
}

// InitOption represents options used in the Init method.
type InitOption interface {
	configureInit(*initConfig)
}

func (opt *BackendOption) configureInit(conf *initConfig) {
	conf.backend = opt.backend
}

func (opt *BackendConfigOption) configureInit(conf *initConfig) {
	conf.backendConfig = append(conf.backendConfig, opt.path)
}

func (opt *DirOption) configureInit(conf *initConfig) {
	conf.dir = opt.path
}

func (opt *ForceCopyOption) configureInit(conf *initConfig) {
	conf.forceCopy = opt.forceCopy
}

func (opt *FromModuleOption) configureInit(conf *initConfig) {
	conf.fromModule = opt.source
}

func (opt *GetOption) configureInit(conf *initConfig) {
	conf.get = opt.get
}

func (opt *GetPluginsOption) configureInit(conf *initConfig) {
	conf.getPlugins = opt.getPlugins
}

func (opt *LockOption) configureInit(conf *initConfig) {
	conf.lock = opt.lock
}

func (opt *LockTimeoutOption) configureInit(conf *initConfig) {
	conf.lockTimeout = opt.timeout
}

func (opt *PluginDirOption) configureInit(conf *initConfig) {
	conf.pluginDir = append(conf.pluginDir, opt.pluginDir)
}

func (opt *ReattachOption) configureInit(conf *initConfig) {
	conf.reattachInfo = opt.info
}

func (opt *ReconfigureOption) configureInit(conf *initConfig) {
	conf.reconfigure = opt.reconfigure
}

func (opt *UpgradeOption) configureInit(conf *initConfig) {
	conf.upgrade = opt.upgrade
}

func (opt *VerifyPluginsOption) configureInit(conf *initConfig) {
	conf.verifyPlugins = opt.verifyPlugins
}

func (tf *Terraform) configureInitOptions(ctx context.Context, c *initConfig, opts ...InitOption) error {
	for _, o := range opts {
		switch o.(type) {
		case *LockOption, *LockTimeoutOption, *VerifyPluginsOption, *GetPluginsOption:
			err := tf.compatible(ctx, nil, tf0_15_0)
			if err != nil {
				return fmt.Errorf("-lock, -lock-timeout, -verify-plugins, and -get-plugins options are no longer available as of Terraform 0.15: %w", err)
			}
		}

		o.configureInit(c)
	}
	return nil
}

// Init represents the terraform init subcommand.
func (tf *Terraform) Init(ctx context.Context, opts ...InitOption) error {
	cmd, err := tf.initCmd(ctx, opts...)
	if err != nil {
		return err
	}
	return tf.runTerraformCmd(ctx, cmd)
}

// InitJSON represents the terraform init subcommand with the `-json` flag.
// Using the `-json` flag will result in
// [machine-readable](https://developer.hashicorp.com/terraform/internals/machine-readable-ui)
// JSON being written to the supplied `io.Writer`.
func (tf *Terraform) InitJSON(ctx context.Context, w io.Writer, opts ...InitOption) error {
	err := tf.compatible(ctx, tf1_9_0, nil)
	if err != nil {
		return fmt.Errorf("terraform init -json was added in 1.9.0: %w", err)
	}

	tf.SetStdout(w)

	cmd, err := tf.initJSONCmd(ctx, opts...)
	if err != nil {
		return err
	}

	return tf.runTerraformCmd(ctx, cmd)
}

func (tf *Terraform) initCmd(ctx context.Context, opts ...InitOption) (*exec.Cmd, error) {
	c := defaultInitOptions

	err := tf.configureInitOptions(ctx, &c, opts...)
	if err != nil {
		return nil, err
	}

	args, err := tf.buildInitArgs(ctx, c)
	if err != nil {
		return nil, err
	}

	// Optional positional argument; must be last as flags precede positional arguments.
	if c.dir != "" {
		args = append(args, c.dir)
	}

	return tf.buildInitCmd(ctx, c, args)
}

func (tf *Terraform) initJSONCmd(ctx context.Context, opts ...InitOption) (*exec.Cmd, error) {
	c := defaultInitOptions

	err := tf.configureInitOptions(ctx, &c, opts...)
	if err != nil {
		return nil, err
	}

	args, err := tf.buildInitArgs(ctx, c)
	if err != nil {
		return nil, err
	}

	args = append(args, "-json")

	// Optional positional argument; must be last as flags precede positional arguments.
	if c.dir != "" {
		args = append(args, c.dir)
	}

	return tf.buildInitCmd(ctx, c, args)
}

func (tf *Terraform) buildInitArgs(ctx context.Context, c initConfig) ([]string, error) {
	args := []string{"init", "-no-color", "-input=false"}

	// string opts: only pass if set
	if c.fromModule != "" {
		args = append(args, "-from-module="+c.fromModule)
	}

	// string opts removed in 0.15: pass if set and <0.15
	err := tf.compatible(ctx, nil, tf0_15_0)
	if err == nil {
		if c.lockTimeout != "" {
			args = append(args, "-lock-timeout="+c.lockTimeout)
		}
	}

	// boolean opts: always pass
	args = append(args, "-backend="+fmt.Sprint(c.backend))
	args = append(args, "-get="+fmt.Sprint(c.get))
	args = append(args, "-upgrade="+fmt.Sprint(c.upgrade))

	// boolean opts removed in 0.15: pass if <0.15
	err = tf.compatible(ctx, nil, tf0_15_0)
	if err == nil {
		args = append(args, "-lock="+fmt.Sprint(c.lock))
		args = append(args, "-get-plugins="+fmt.Sprint(c.getPlugins))
		args = append(args, "-verify-plugins="+fmt.Sprint(c.verifyPlugins))
	}

	if c.forceCopy {
		args = append(args, "-force-copy")
	}

	// unary flags: pass if true
	if c.reconfigure {
		args = append(args, "-reconfigure")
	}

	// string slice opts: split into separate args
	if c.backendConfig != nil {
		for _, bc := range c.backendConfig {
			args = append(args, "-backend-config="+bc)
		}
	}
	if c.pluginDir != nil {
		for _, pd := range c.pluginDir {
			args = append(args, "-plugin-dir="+pd)
		}
	}

	return args, nil
}

func (tf *Terraform) buildInitCmd(ctx context.Context, c initConfig, args []string) (*exec.Cmd, error) {
	mergeEnv := map[string]string{}
	if c.reattachInfo != nil {
		reattachStr, err := c.reattachInfo.marshalString()
		if err != nil {
			return nil, err
		}
		mergeEnv[reattachEnvVar] = reattachStr
	}

	return tf.buildTerraformCmd(ctx, mergeEnv, args...), nil
}
