package segments

import (
	"encoding/json"
	"errors"
	"path/filepath"
	"slices"
	"strings"

	"github.com/jandedobbeleer/oh-my-posh/src/segments/options"
)

const (
	Command options.Option = "command"
)

type Terraform struct {
	Base

	TerraformBlock
	WorkspaceName string
}

func (tf *Terraform) Template() string {
	return " {{ .WorkspaceName }}{{ if .Version }} {{ .Version }}{{ end }} "
}

type TerraformBlock struct {
	Version *string `json:"terraform_version"`
}

func (tf *Terraform) Enabled() bool {
	cmd := tf.options.String(Command, "terraform")
	fetchVersion := tf.options.Bool(options.FetchVersion, false)

	if !tf.env.HasCommand(cmd) || !tf.inContext(fetchVersion) {
		return false
	}

	tf.WorkspaceName, _ = tf.env.RunCommand(cmd, "workspace", "show")
	if !fetchVersion {
		return true
	}

	// tenv (https://github.com/tofuutils/tenv) pins the version through an
	// environment variable or a version file, which takes precedence over the
	// version declared in the terraform files or the state file.
	if tf.setVersionFromTenv() {
		return true
	}

	if err := tf.setVersionFromTfFiles(); err == nil {
		return true
	}

	tf.setVersionFromTfStateFile()
	return true
}

func (tf *Terraform) tenvSources() (envVar, versionFile string) {
	cmd := tf.options.String(Command, "terraform")
	if strings.Contains(cmd, "tofu") {
		return "TOFUENV_TOFU_VERSION", ".opentofu-version"
	}

	return "TFENV_TERRAFORM_VERSION", ".terraform-version"
}

func (tf *Terraform) setVersionFromTenv() bool {
	envVar, versionFile := tf.tenvSources()

	// the environment variable takes precedence over the version file
	if version := strings.TrimSpace(tf.env.Getenv(envVar)); version != "" {
		tf.Version = &version
		return true
	}

	if !tf.env.HasFiles(versionFile) {
		return false
	}

	version := strings.TrimSpace(tf.env.FileContent(versionFile))
	// a version file holds a single version reference, guard against trailing content
	if line, _, found := strings.Cut(version, "\n"); found {
		version = strings.TrimSpace(line)
	}

	if version == "" {
		return false
	}

	tf.Version = &version
	return true
}

func (tf *Terraform) inContext(fetchVersion bool) bool {
	terraformFolder := filepath.Join(tf.env.Pwd(), ".terraform")

	if tf.env.HasFolder(terraformFolder) {
		return true
	}

	files := []string{".tf", ".tfplan", ".tfstate"}
	if slices.ContainsFunc(files, tf.env.HasFiles) {
		return true
	}

	if !fetchVersion {
		return false
	}

	_, tenvVersionFile := tf.tenvSources()
	versionFiles := []string{"versions.tf", "main.tf", "terraform.tfstate", tenvVersionFile}
	return slices.ContainsFunc(versionFiles, tf.env.HasFiles)
}

func (tf *Terraform) setVersionFromTfFiles() error {
	files := []string{"versions.tf", "main.tf"}
	for _, file := range files {
		if !tf.env.HasFiles(file) {
			continue
		}

		content := tf.env.FileContent(file)
		version, ok := extractRequiredVersion(content)
		if !ok {
			continue
		}

		tf.Version = &version
		return nil
	}
	return errors.New("no valid terraform files found")
}

func (tf *Terraform) setVersionFromTfStateFile() {
	file := "terraform.tfstate"
	if !tf.env.HasFiles(file) {
		return
	}

	content := tf.env.FileContent(file)
	_ = json.Unmarshal([]byte(content), &tf.TerraformBlock)
}
