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

package tfexec

import (
	"context"
	"fmt"
	"os/exec"
	"strings"

	tfjson "github.com/hashicorp/terraform-json"
)

type showConfig struct {
	reattachInfo ReattachInfo
	jsonNumber   *UseJSONNumberOption
}

var defaultShowOptions = showConfig{}

type ShowOption interface {
	configureShow(*showConfig)
}

func (opt *ReattachOption) configureShow(conf *showConfig) {
	conf.reattachInfo = opt.info
}

func (opt *UseJSONNumberOption) configureShow(conf *showConfig) {
	conf.jsonNumber = opt
}

// Show reads the default state path and outputs the state.
// To read a state or plan file, ShowState or ShowPlan must be used instead.
func (tf *Terraform) Show(ctx context.Context, opts ...ShowOption) (*tfjson.State, error) {
	err := tf.compatible(ctx, tf0_12_0, nil)
	if err != nil {
		return nil, fmt.Errorf("terraform show -json was added in 0.12.0: %w", err)
	}

	c := defaultShowOptions

	for _, o := range opts {
		o.configureShow(&c)
	}

	mergeEnv := map[string]string{}
	if c.reattachInfo != nil {
		reattachStr, err := c.reattachInfo.marshalString()
		if err != nil {
			return nil, err
		}
		mergeEnv[reattachEnvVar] = reattachStr
	}

	showCmd := tf.showCmd(ctx, true, mergeEnv)

	var ret tfjson.State
	ret.UseJSONNumber(true)

	if c.jsonNumber != nil {
		ret.UseJSONNumber(c.jsonNumber.useJSONNumber)
	}

	err = tf.runTerraformCmdJSON(ctx, showCmd, &ret)
	if err != nil {
		return nil, err
	}

	err = ret.Validate()
	if err != nil {
		return nil, err
	}

	return &ret, nil
}

// ShowStateFile reads a given state file and outputs the state.
func (tf *Terraform) ShowStateFile(ctx context.Context, statePath string, opts ...ShowOption) (*tfjson.State, error) {
	err := tf.compatible(ctx, tf0_12_0, nil)
	if err != nil {
		return nil, fmt.Errorf("terraform show -json was added in 0.12.0: %w", err)
	}

	if statePath == "" {
		return nil, fmt.Errorf("statePath cannot be blank: use Show() if not passing statePath")
	}

	c := defaultShowOptions

	for _, o := range opts {
		o.configureShow(&c)
	}

	mergeEnv := map[string]string{}
	if c.reattachInfo != nil {
		reattachStr, err := c.reattachInfo.marshalString()
		if err != nil {
			return nil, err
		}
		mergeEnv[reattachEnvVar] = reattachStr
	}

	showCmd := tf.showCmd(ctx, true, mergeEnv, statePath)

	var ret tfjson.State
	ret.UseJSONNumber(true)

	if c.jsonNumber != nil {
		ret.UseJSONNumber(c.jsonNumber.useJSONNumber)
	}

	err = tf.runTerraformCmdJSON(ctx, showCmd, &ret)
	if err != nil {
		return nil, err
	}

	err = ret.Validate()
	if err != nil {
		return nil, err
	}

	return &ret, nil
}

// ShowPlanFile reads a given plan file and outputs the plan.
func (tf *Terraform) ShowPlanFile(ctx context.Context, planPath string, opts ...ShowOption) (*tfjson.Plan, error) {
	err := tf.compatible(ctx, tf0_12_0, nil)
	if err != nil {
		return nil, fmt.Errorf("terraform show -json was added in 0.12.0: %w", err)
	}

	if planPath == "" {
		return nil, fmt.Errorf("planPath cannot be blank: use Show() if not passing planPath")
	}

	c := defaultShowOptions

	for _, o := range opts {
		o.configureShow(&c)
	}

	mergeEnv := map[string]string{}
	if c.reattachInfo != nil {
		reattachStr, err := c.reattachInfo.marshalString()
		if err != nil {
			return nil, err
		}
		mergeEnv[reattachEnvVar] = reattachStr
	}

	showCmd := tf.showCmd(ctx, true, mergeEnv, planPath)

	var ret tfjson.Plan

	if c.jsonNumber != nil {
		ret.UseJSONNumber(c.jsonNumber.useJSONNumber)
	}

	err = tf.runTerraformCmdJSON(ctx, showCmd, &ret)
	if err != nil {
		return nil, err
	}

	err = ret.Validate()
	if err != nil {
		return nil, err
	}

	return &ret, nil

}

// ShowPlanFileRaw reads a given plan file and outputs the plan in a
// human-friendly, opaque format.
func (tf *Terraform) ShowPlanFileRaw(ctx context.Context, planPath string, opts ...ShowOption) (string, error) {
	if planPath == "" {
		return "", fmt.Errorf("planPath cannot be blank: use Show() if not passing planPath")
	}

	c := defaultShowOptions

	for _, o := range opts {
		o.configureShow(&c)
	}

	mergeEnv := map[string]string{}
	if c.reattachInfo != nil {
		reattachStr, err := c.reattachInfo.marshalString()
		if err != nil {
			return "", err
		}
		mergeEnv[reattachEnvVar] = reattachStr
	}

	showCmd := tf.showCmd(ctx, false, mergeEnv, planPath)

	var outBuf strings.Builder
	showCmd.Stdout = &outBuf
	err := tf.runTerraformCmd(ctx, showCmd)
	if err != nil {
		return "", err
	}

	return outBuf.String(), nil

}

func (tf *Terraform) showCmd(ctx context.Context, jsonOutput bool, mergeEnv map[string]string, args ...string) *exec.Cmd {
	allArgs := []string{"show"}
	if jsonOutput {
		allArgs = append(allArgs, "-json")
	}
	allArgs = append(allArgs, "-no-color")
	allArgs = append(allArgs, args...)

	return tf.buildTerraformCmd(ctx, mergeEnv, allArgs...)
}
