package subcmd

import (
	"archive/zip"
	"context"
	"errors"
	"flag"
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path"
	"strings"

	"github.com/google/subcommands"
)

// BuildLambdas creates a guardian app task definition
type BuildLambdas struct {
	Logger *log.Logger

	out string
}

// Name implements subcommands
func (cmd BuildLambdas) Name() string {
	return "build-lambdas"
}

// Synopsis implements subcommands
func (cmd BuildLambdas) Synopsis() string {
	return "Build lambda zips."
}

// Usage implements subcommands
func (cmd BuildLambdas) Usage() string {
	return `build-lambdas

`
}

// SetFlags implements subcommands
func (cmd *BuildLambdas) SetFlags(f *flag.FlagSet) {
	f.StringVar(&cmd.out, "out", "", "Base directory to output lambdas at.")
}

// Execute implements subcommands
func (cmd BuildLambdas) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
	if err := cmd.execute(ctx); err != nil {
		cmd.Logger.Print(err)
		return subcommands.ExitFailure
	}

	return subcommands.ExitSuccess
}

func (cmd BuildLambdas) execute(ctx context.Context) error {
	if cmd.out == "" {
		return errors.New("-out is required")
	}

	lambdas, err := cmd.listLambdas()
	if err != nil {
		return err
	}

	if err := os.MkdirAll(cmd.out, os.ModePerm); err != nil {
		return err
	}

	for _, lambda := range lambdas {
		goPkg := "code.justin.tv/beefcake/server/lambdas/" + lambda
		outPath := path.Join(cmd.out, lambda+".zip")
		cmd.Logger.Printf("building %s to %s", goPkg, outPath)
		if err := cmd.buildLambda(ctx, goPkg, outPath); err != nil {
			return err
		}
	}
	return nil
}

func (cmd BuildLambdas) listLambdas() ([]string, error) {
	dirs, err := ioutil.ReadDir("./lambdas")
	if err != nil {
		return nil, err
	}

	lambdas := make([]string, 0)
	for _, dir := range dirs {
		if strings.HasPrefix(dir.Name(), ".") {
			continue
		}

		lambdas = append(lambdas, dir.Name())
	}
	return lambdas, nil
}

func (cmd BuildLambdas) buildLambda(ctx context.Context, pkg, outPath string) (err error) {
	tmpdir, err := ioutil.TempDir("", "")
	if err != nil {
		return err
	}
	defer func() {
		if rmErr := os.RemoveAll(tmpdir); rmErr != nil && err == nil {
			err = rmErr
		}
	}()

	mainPath := path.Join(tmpdir, "main")
	buildCmd := exec.CommandContext(
		ctx,
		"go", "build", "-o", mainPath, pkg,
	)
	buildCmd.Env = append(os.Environ(), "GOOS=linux")
	if err := buildCmd.Run(); err != nil {
		return err
	}

	out, err := os.Create(outPath)
	if err != nil {
		return err
	}
	defer func() {
		if subErr := out.Close(); subErr != nil && err == nil {
			err = subErr
		}
	}()

	return cmd.writeZip(mainPath, out)
}

func (cmd BuildLambdas) writeZip(mainPath string, out io.Writer) (err error) {
	zipOut := zip.NewWriter(out)

	fi, err := os.Stat(mainPath)
	if err != nil {
		return err
	}

	fh := &zip.FileHeader{Name: fi.Name()}
	fh.SetMode(fi.Mode())

	mainOut, err := zipOut.CreateHeader(fh)
	if err != nil {
		return err
	}

	if err := cmd.copyFile(mainOut, mainPath); err != nil {
		return err
	}

	return zipOut.Close()
}

func (cmd BuildLambdas) copyFile(dst io.Writer, srcPath string) (err error) {
	src, err := os.Open(srcPath)
	if err != nil {
		return err
	}
	defer func() {
		if subErr := src.Close(); subErr != nil && err == nil {
			err = subErr
		}
	}()

	_, err = io.Copy(dst, src)
	return err
}
