package tools

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"os/exec"

	"github.com/hashicorp/go-version"
	"github.com/hashicorp/hc-install/product"
	"github.com/hashicorp/hc-install/releases"
	"github.com/hashicorp/terraform-exec/tfexec"
	tfjson "github.com/hashicorp/terraform-json"
)

func Terraform(workingDir string) *terraform {
	return &terraform{
		workingDir: workingDir,
		version:    "1.14.5",
		logger:     tfLogParser{},
		vars:       []string{},
	}
}

type (
	terraform struct {
		tf         *tfexec.Terraform
		state      *tfjson.State
		workingDir string
		version    string
		logger     io.Writer
		vars       []string
	}
	tfLogParser struct{}
)

func (_ tfLogParser) Write(p []byte) (n int, err error) {
	var logEntry tfjson.LogMessage
	err = json.Unmarshal(p, &logEntry)
	if err != nil {
		return 0, err
	}
	var msg []byte
	if logEntry.Level() == "" {
		msg = []byte(fmt.Sprintf("%s\n", logEntry.Message()))
	} else {
		msg = []byte(fmt.Sprintf("[%s] %s\n", logEntry.Level(), logEntry.Message()))
	}
	log.Write(msg)

	return len(msg), nil
}

func (t *terraform) VarsFile(path string) *terraform {
	t.vars = append(t.vars, path)
	return t
}

func (t *terraform) Version(version string) *terraform {
	t.version = version
	return t
}

func (t *terraform) Init() error {
	var err error
	var execPath = ""
	var install = true

	execPath, err = exec.LookPath("terraform")
	if err == nil {
		t.tf, err = tfexec.NewTerraform(t.workingDir, execPath)
		if err == nil {
			v, _, err := t.tf.Version(context.Background(), true)
			if err == nil {
				if v.String() == t.version {
					install = false
				}
			}
		}
	}

	if install {
		log.Write([]byte(fmt.Sprintf("Installing Terraform version: %s", t.version)))

		installer := &releases.ExactVersion{
			Product: product.Terraform,
			Version: version.Must(version.NewVersion(t.version)),
		}

		execPath, err = installer.Install(context.Background())
		if err != nil {
			return err
		}
	}

	t.tf, err = tfexec.NewTerraform(t.workingDir, execPath)
	if err != nil {
		return err
	}

	err = t.tf.InitJSON(context.Background(),
		t.logger,
		tfexec.Upgrade(true),
	)
	if err != nil {
		return err
	}

	t.state, err = t.tf.Show(context.Background())
	if err != nil {
		return err
	}

	return nil
}
func (t *terraform) Plan() (*tfjson.Plan, error) {
	if t.tf == nil {
		err := t.Init()
		if err != nil {
			return nil, err
		}
	}

	opts := []tfexec.PlanOption{
		tfexec.Out("terraform.plan"),
	}

	for _, f := range t.vars {
		tfexec.VarFile(f)
	}

	_, err := t.tf.PlanJSON(context.Background(),
		t.logger,
		opts...,
	)
	if err != nil {
		return nil, err
	}

	plan, err := t.tf.ShowPlanFile(context.Background(), t.workingDir+"/terraform.plan")
	if err != nil {
		return nil, err
	}

	return plan, nil
}
func (t *terraform) Apply() error {
	if t.tf == nil {
		err := t.Init()
		if err != nil {
			return err
		}
	}

	err := t.tf.ApplyJSON(context.Background(), t.logger)
	if err != nil {
		return err
	}

	t.state, err = t.tf.Show(context.Background())
	if err != nil {
		return err
	}

	return nil
}
func (t *terraform) Show() error {
	if t.tf == nil {
		err := t.Init()
		if err != nil {
			return err
		}
	}

	var err error
	t.state, err = t.tf.Show(context.Background())
	if err != nil {
		return err
	}

	return nil
}
func (t *terraform) Outputs() map[string]any {
	outputs := map[string]any{}
	if t.state != nil {
		if t.state.Values != nil {
			if t.state.Values.Outputs != nil {
				for k, v := range t.state.Values.Outputs {
					outputs[k] = v.Value
				}
			}
		}
	}

	return outputs
}
func (t *terraform) State() *tfjson.StateModule {
	if t.state != nil {
		if t.state.Values != nil {
			return t.state.Values.RootModule
		}
	}

	return nil
}
func (t *terraform) Destroy() error {
	if t.tf == nil {
		err := t.Init()
		if err != nil {
			return err
		}
	}

	err := t.tf.DestroyJSON(context.Background(), t.logger)
	if err != nil {
		return err
	}

	t.state, err = t.tf.Show(context.Background())
	if err != nil {
		return err
	}

	return nil
}
