package main

import (
	"encoding/json"
	"flag"
	"log/slog"
	"os"

	"github.com/joint-online-judge/JOJ3/internal/executors"
	_ "github.com/joint-online-judge/JOJ3/internal/parsers"
	"github.com/joint-online-judge/JOJ3/internal/stage"

	"github.com/go-git/go-git/v5"
	"github.com/jinzhu/copier"
)

func setupSlog(logLevel int) {
	lvl := new(slog.LevelVar)
	lvl.Set(slog.Level(logLevel))
	opts := &slog.HandlerOptions{Level: lvl}
	handler := slog.NewTextHandler(os.Stderr, opts)
	logger := slog.New(handler)
	slog.SetDefault(logger)
}

func getCommitMsg() (msg string, err error) {
	r, err := git.PlainOpen(".")
	if err != nil {
		return
	}
	ref, err := r.Head()
	if err != nil {
		return
	}
	commit, err := r.CommitObject(ref.Hash())
	if err != nil {
		return
	}
	msg = commit.Message
	return
}

func generateStages(conf Conf) ([]stage.Stage, error) {
	stages := []stage.Stage{}
	for _, s := range conf.Stages {
		var cmds []stage.Cmd
		defaultCmd := s.Executor.With.Default
		for _, optionalCmd := range s.Executor.With.Cases {
			cmd := s.Executor.With.Default
			err := copier.Copy(&cmd, &optionalCmd)
			if err != nil {
				slog.Error("generate stages", "error", err)
				return stages, err
			}
			// since these 3 values are pointers, copier will always copy
			// them, so we need to check them manually
			if defaultCmd.Stdin != nil && optionalCmd.Stdin == nil {
				cmd.Stdin = defaultCmd.Stdin
			}
			if defaultCmd.Stdout != nil && optionalCmd.Stdout == nil {
				cmd.Stdout = defaultCmd.Stdout
			}
			if defaultCmd.Stderr != nil && optionalCmd.Stderr == nil {
				cmd.Stderr = defaultCmd.Stderr
			}
			cmds = append(cmds, cmd)
		}
		if len(s.Executor.With.Cases) == 0 {
			cmds = []stage.Cmd{defaultCmd}
		}
		slog.Debug("parse stages conf", "cmds", cmds)
		stages = append(stages, stage.Stage{
			Name:         s.Name,
			ExecutorName: s.Executor.Name,
			ExecutorCmds: cmds,
			ParserName:   s.Parser.Name,
			ParserConf:   s.Parser.With,
		})
	}
	return stages, nil
}

func outputResult(outputPath string, results []stage.StageResult) error {
	content, err := json.Marshal(results)
	if err != nil {
		return err
	}
	return os.WriteFile(outputPath,
		append(content, []byte("\n")...), 0o600)
}

var (
	metaConfPath string
	msg          string
)

func init() {
	flag.StringVar(&metaConfPath, "meta-conf", "meta-conf.toml", "meta config file path")
	flag.StringVar(&msg, "msg", "", "message to trigger the running, leave empty to use git commit message on HEAD")
}

func mainImpl() error {
	setupSlog(int(slog.LevelInfo)) // before conf is loaded
	flag.Parse()
	if msg == "" {
		var err error
		msg, err = getCommitMsg()
		if err != nil {
			slog.Error("get commit msg", "error", err)
			return err
		}
	}
	conf, err := msgToConf(metaConfPath, msg)
	if err != nil {
		slog.Error("no conf found", "error", err)
		return err
	}
	setupSlog(conf.LogLevel) // after conf is loaded
	executors.InitWithConf(conf.SandboxExecServer, conf.SandboxToken)
	stages, err := generateStages(conf)
	if err != nil {
		slog.Error("generate stages", "error", err)
		return err
	}
	defer stage.Cleanup()
	results, err := stage.Run(stages)
	if err != nil {
		slog.Error("run stages", "error", err)
		return err
	}
	if err := outputResult(conf.OutputPath, results); err != nil {
		slog.Error("output result", "error", err)
		return err
	}
	return nil
}

func main() {
	if err := mainImpl(); err != nil {
		os.Exit(1)
	}
}