package configupgrade

import (
	"bytes"
	"fmt"

	"github.com/hashicorp/terraform/tfdiags"

	hcl2 "github.com/hashicorp/hcl/v2"
	hcl2write "github.com/hashicorp/hcl/v2/hclwrite"
)

// Upgrade takes some input module sources and produces a new ModuleSources
// that should be equivalent to the input but use the configuration idioms
// associated with the new configuration loader.
//
// The result of this function will probably not be accepted by this function,
// because it will contain constructs that are known only to the new
// loader.
//
// The result may include additional files that were not present in the
// input. The result may also include nil entries for filenames that were
// present in the input, indicating that these files should be deleted.
// In particular, file renames are represented as a new entry accompanied
// by a nil entry for the old name.
//
// If the returned diagnostics contains errors, the caller should not write
// the resulting sources to disk since they will probably be incomplete. If
// only warnings are present then the files may be written to disk. Most
// warnings are also represented as "TF-UPGRADE-TODO:" comments in the
// generated source files so that users can visit them all and decide what to
// do with them.
func (u *Upgrader) Upgrade(input ModuleSources, dir string) (ModuleSources, tfdiags.Diagnostics) {
	ret := make(ModuleSources)
	var diags tfdiags.Diagnostics

	an, err := u.analyze(input)
	if err != nil {
		diags = diags.Append(err)
		return ret, diags
	}
	an.ModuleDir = dir

	for name, src := range input {
		ext := fileExt(name)
		if ext == "" {
			// This should never happen because we ignore files that don't
			// have our conventional extensions during LoadModule, but we'll
			// silently pass through such files assuming that the caller
			// has been tampering with the sources map somehow.
			ret[name] = src
			continue
		}

		isJSON := (ext == ".tf.json")

		// The legacy loader allowed JSON syntax inside files named just .tf,
		// so we'll detect that case and rename them here so that the new
		// loader will accept the JSON. However, JSON files are usually
		// generated so we'll also generate a warning to the user to update
		// whatever program generated the file to use the new name.
		if !isJSON {
			trimSrc := bytes.TrimSpace(src)
			if len(trimSrc) > 0 && (trimSrc[0] == '{' || trimSrc[0] == '[') {
				isJSON = true

				// Rename in the output
				ret[name] = nil // mark for deletion
				oldName := name
				name = input.UnusedFilename(name + ".json")
				ret[name] = src

				diags = diags.Append(&hcl2.Diagnostic{
					Severity: hcl2.DiagWarning,
					Summary:  "JSON configuration file was renamed",
					Detail: fmt.Sprintf(
						"The file %q appears to be in JSON format, so it was renamed to %q. If this file is generated by another program, that program must be updated to use this new name.",
						oldName, name,
					),
				})
				continue
			}
		}

		if isJSON {
			// We don't do any automatic rewriting for JSON files, since they
			// are usually generated and thus it's the generating program that
			// needs to be updated, rather than its output.
			diags = diags.Append(&hcl2.Diagnostic{
				Severity: hcl2.DiagWarning,
				Summary:  "JSON configuration file was not rewritten",
				Detail: fmt.Sprintf(
					"The JSON configuration file %q was skipped, because JSON files are assumed to be generated. The program that generated this file may need to be updated for changes to the configuration language.",
					name,
				),
			})
			ret[name] = src // unchanged
			continue
		}

		// TODO: Actually rewrite this .tf file.
		result, fileDiags := u.upgradeNativeSyntaxFile(name, src, an)
		diags = diags.Append(fileDiags)
		if fileDiags.HasErrors() {
			// Leave unchanged, then.
			ret[name] = src
			continue
		}

		ret[name] = hcl2write.Format(result.Content)
	}

	versionsName := ret.UnusedFilename("versions.tf")
	ret[versionsName] = []byte(newVersionConstraint)

	return ret, diags
}

const newVersionConstraint = `
terraform {
  required_version = ">= 0.12"
}
`
