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

package tfexec

import (
	"context"
	"fmt"
	"io"
	"os/exec"
	"path/filepath"
	"strings"
)

type formatConfig struct {
	recursive bool
	dir       string
}

var defaultFormatConfig = formatConfig{
	recursive: false,
}

type FormatOption interface {
	configureFormat(*formatConfig)
}

func (opt *RecursiveOption) configureFormat(conf *formatConfig) {
	conf.recursive = opt.recursive
}

func (opt *DirOption) configureFormat(conf *formatConfig) {
	conf.dir = opt.path
}

// FormatString formats a passed string, given a path to Terraform.
func FormatString(ctx context.Context, execPath string, content string) (string, error) {
	tf, err := NewTerraform(filepath.Dir(execPath), execPath)
	if err != nil {
		return "", err
	}

	return tf.FormatString(ctx, content)
}

// FormatString formats a passed string.
func (tf *Terraform) FormatString(ctx context.Context, content string) (string, error) {
	in := strings.NewReader(content)
	var outBuf strings.Builder
	err := tf.Format(ctx, in, &outBuf)
	if err != nil {
		return "", err
	}
	return outBuf.String(), nil
}

// Format performs formatting on the unformatted io.Reader (as stdin to the CLI) and returns
// the formatted result on the formatted io.Writer.
func (tf *Terraform) Format(ctx context.Context, unformatted io.Reader, formatted io.Writer) error {
	cmd, err := tf.formatCmd(ctx, nil, Dir("-"))
	if err != nil {
		return err
	}

	cmd.Stdin = unformatted
	cmd.Stdout = mergeWriters(cmd.Stdout, formatted)

	return tf.runTerraformCmd(ctx, cmd)
}

// FormatWrite attempts to format and modify all config files in the working or selected (via DirOption) directory.
func (tf *Terraform) FormatWrite(ctx context.Context, opts ...FormatOption) error {
	for _, o := range opts {
		switch o := o.(type) {
		case *DirOption:
			if o.path == "-" {
				return fmt.Errorf("a path of \"-\" is not supported for this method, please use FormatString")
			}
		}
	}

	cmd, err := tf.formatCmd(ctx, []string{"-write=true", "-list=false", "-diff=false"}, opts...)
	if err != nil {
		return err
	}

	return tf.runTerraformCmd(ctx, cmd)
}

// FormatCheck returns true if the config files in the working or selected (via DirOption) directory are already formatted.
func (tf *Terraform) FormatCheck(ctx context.Context, opts ...FormatOption) (bool, []string, error) {
	for _, o := range opts {
		switch o := o.(type) {
		case *DirOption:
			if o.path == "-" {
				return false, nil, fmt.Errorf("a path of \"-\" is not supported for this method, please use FormatString")
			}
		}
	}

	cmd, err := tf.formatCmd(ctx, []string{"-write=false", "-list=true", "-diff=false", "-check=true"}, opts...)
	if err != nil {
		return false, nil, err
	}

	var outBuf strings.Builder
	cmd.Stdout = mergeWriters(cmd.Stdout, &outBuf)

	err = tf.runTerraformCmd(ctx, cmd)
	if err == nil {
		return true, nil, nil
	}
	if cmd.ProcessState.ExitCode() == 3 {
		// unformatted, parse the file list

		files := []string{}
		lines := strings.Split(strings.Replace(outBuf.String(), "\r\n", "\n", -1), "\n")
		for _, l := range lines {
			l = strings.TrimSpace(l)
			if l == "" {
				continue
			}
			files = append(files, l)
		}

		return false, files, nil
	}
	return false, nil, err
}

func (tf *Terraform) formatCmd(ctx context.Context, args []string, opts ...FormatOption) (*exec.Cmd, error) {
	err := tf.compatible(ctx, tf0_7_7, nil)
	if err != nil {
		return nil, fmt.Errorf("fmt was first introduced in Terraform 0.7.7: %w", err)
	}

	c := defaultFormatConfig

	for _, o := range opts {
		switch o.(type) {
		case *RecursiveOption:
			err := tf.compatible(ctx, tf0_12_0, nil)
			if err != nil {
				return nil, fmt.Errorf("-recursive was added to fmt in Terraform 0.12: %w", err)
			}
		}

		o.configureFormat(&c)
	}

	args = append([]string{"fmt", "-no-color"}, args...)

	if c.recursive {
		args = append(args, "-recursive")
	}

	if c.dir != "" {
		args = append(args, c.dir)
	}

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