package terraform

import (
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"sync"
)

type Instance struct {
	tmpDir string
}

// New takes a remote location, the address in consul to store the data and a
// config file. The config file will get written to a new temporary directory.
//
// This will then return a Instance that can be used to figure call terraform
// actions.
func New(remote string, configFile io.Reader) (*Instance, error) {
	errStr := "Error creating a new terraform instance: %v"

	name, err := ioutil.TempDir("", "terraform")
	if err != nil {
		return nil, fmt.Errorf(errStr, err)
	}
	i := &Instance{name}

	f, err := os.OpenFile(
		filepath.Join(i.tmpDir, "main.tf"),
		os.O_TRUNC|os.O_RDWR|os.O_CREATE,
		0500,
	)
	if err != nil {
		return nil, fmt.Errorf(errStr, err)
	}

	if _, err := io.Copy(f, configFile); err != nil {
		return nil, fmt.Errorf(errStr, err)
	}
	f.Close()

	if out, err := i.configureRemote(remote); err != nil {
		log.Printf("Error configuring remote: %v", out)
		return nil, fmt.Errorf(errStr, err)
	}

	return i, nil
}

// runTerraform takes a collection of arguments to pass to terraform. It will
// then return the stdout, the stderr and an error if terraform failed to run.
func (i *Instance) runTerraform(args ...string) ([]byte, []byte, error) {
	cmd := exec.Command("terraform", args...)
	cmd.Env = append(cmd.Env, "PATH=/usr/local/bin:/bin")
	cmd.Dir = i.tmpDir

	stderrPipe, err := cmd.StderrPipe()
	if err != nil {
		return nil, nil, err
	}

	stdoutPipe, err := cmd.StdoutPipe()
	if err != nil {
		return nil, nil, err
	}

	wg := sync.WaitGroup{}
	stdout, stderr := []byte{}, []byte{}
	errs := make(chan error, 2)

	wg.Add(2)

	go func() {
		defer wg.Done()
		var err error
		stdout, err = ioutil.ReadAll(stdoutPipe)
		if err != nil {
			errs <- err
			return
		}
	}()

	go func() {
		defer wg.Done()
		var err error
		stderr, err = ioutil.ReadAll(stderrPipe)
		if err != nil {
			errs <- err
			return
		}
	}()

	if err := cmd.Start(); err != nil {
		return nil, nil, fmt.Errorf("Error calling terraform: %v", err)
	}

	if err := cmd.Wait(); err != nil {
		return nil, nil, fmt.Errorf("Error running terraform: %v", err)
	}

	wg.Wait()

	// We don't want to block when reading, so use a select with an empty
	// default.
	select {
	case err := <-errs:
		return nil, nil, fmt.Errorf("Error reading stdout/err: %v", err)
	default:
	}

	return stdout, stderr, nil
}

func (i *Instance) configureRemote(remotePath string) (string, error) {
	stdout, stderr, err := i.runTerraform(
		"remote", "config", "-backend=consul",
		fmt.Sprintf(`-backend-config=path=%s`, remotePath),
		`-backend-config=address=consul.internal.justin.tv:80`,
	)
	if err != nil {
		return string(stderr), err
	}

	return string(stdout), nil
}

func (i *Instance) Plan() (string, error) {
	stdout, stderr, err := i.runTerraform("plan", "-module-depth=-1", "-out", "plan.out")
	if err != nil {
		return string(stderr), err
	}

	return string(stdout), nil
}
