package xcode

import (
	"fmt"
	executorpkg "github.com/cirruslabs/tart/benchmark/internal/executor"
	"github.com/gosuri/uitable"
	"github.com/spf13/cobra"
	"go.uber.org/zap"
	"go.uber.org/zap/zapio"
	"os"
	"os/exec"
)

var debug bool
var image string
var prepare string

func NewCommand() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "xcode",
		Short: "run XCode benchmarks",
		RunE:  run,
	}

	cmd.Flags().BoolVar(&debug, "debug", false, "enable debug logging")
	cmd.Flags().StringVar(&image, "image", "ghcr.io/cirruslabs/macos-tahoe-xcode:latest", "image to use for testing")
	cmd.Flags().StringVar(&prepare, "prepare", "", "command to run before running each benchmark")

	return cmd
}

func run(cmd *cobra.Command, args []string) error {
	config := zap.NewProductionConfig()
	if debug {
		config.Level = zap.NewAtomicLevelAt(zap.DebugLevel)
	}
	logger, err := config.Build()
	if err != nil {
		return err
	}
	defer func() {
		_ = logger.Sync()
	}()

	table := uitable.New()
	table.AddRow("Name", "Executor", "Time")

	for _, benchmark := range benchmarks {
		for _, executorInitializer := range executorpkg.DefaultInitializers(cmd.Context(), image, logger) {
			if prepare != "" {
				shell := "/bin/sh"

				if shellFromEnv, ok := os.LookupEnv("SHELL"); ok {
					shell = shellFromEnv
				}

				logger.Sugar().Infof("running prepare command %q using shell %q",
					prepare, shell)

				cmd := exec.CommandContext(cmd.Context(), shell, "-c", prepare)

				loggerWriter := &zapio.Writer{Log: logger, Level: zap.DebugLevel}

				cmd.Stdout = loggerWriter
				cmd.Stderr = loggerWriter

				if err := cmd.Run(); err != nil {
					return fmt.Errorf("failed to run prepare command %q: %v", prepare, err)
				}
			}

			logger.Sugar().Infof("initializing executor %s", executorInitializer.Name)

			executor, err := executorInitializer.Fn()
			if err != nil {
				return err
			}

			logger.Sugar().Infof("running benchmark %q on %s executor", benchmark.Name,
				executorInitializer.Name)

			stdout, err := executor.Run(cmd.Context(), benchmark.Command)
			if err != nil {
				return err
			}

			output, err := ParseOutput(string(stdout))
			if err != nil {
				return err
			}

			duration := output.Ended.Sub(output.Started)

			logger.Sugar().Infof("Xcode benchmark duration: %s", duration)

			table.AddRow(benchmark.Name, executorInitializer.Name, duration)

			if err := executor.Close(); err != nil {
				return fmt.Errorf("failed to close executor %s: %w",
					executorInitializer.Name, err)
			}
		}
	}

	fmt.Println(table.String())

	return nil
}
