// Copyright 2023 schukai GmbH
// SPDX-License-Identifier: AGPL-3.0

package jobqueue

import (
	"context"
	"fmt"
	"os"
	"os/exec"
	"strings"
	"sync"
)

func NewShellRunnableFromMap(data map[string]interface{}) (*ShellRunnable, error) {

	scriptPath, _ := data["scriptpath"].(string)
	script, _ := data["script"].(string)

	if scriptPath != "" && script != "" {
		return nil, fmt.Errorf("%w: ScriptPath and Script are mutually exclusive", ErrInvalidData)
	}

	if scriptPath == "" && script == "" {
		return nil, fmt.Errorf("%w: ScriptPath or Script is required", ErrInvalidData)
	}

	return &ShellRunnable{
		ScriptPath: scriptPath,
		Script:     script,
	}, nil
}

// ShellResult is a result of a shell script
type ShellResult struct {
	Output   string
	Error    string
	ExitCode int
}

func (s *ShellResult) GetResult() string {
	return s.Output
}

func (s *ShellResult) GetError() (string, int) {
	if s.ExitCode != 0 {
		return s.Error, s.ExitCode
	}
	return "", 0
}

type ShellRunnable struct {
	ScriptPath string
	Script     string
	mu         sync.Mutex
}

func (s *ShellRunnable) Run(ctx context.Context) (RunResult[ShellResult], error) {

	scriptPath := s.ScriptPath

	if s.Script != "" {
		// write to temp
		tmp, err := os.CreateTemp("", "script-*.sh")
		if err != nil {
			Error("Failed to create temp file: %v", err)
			return RunResult[ShellResult]{
				Status: ResultStatusFailed,
				Data: ShellResult{
					Output:   "",
					ExitCode: DefaultErrorExitCode,
					Error:    fmt.Errorf("%w: %v", ErrFailedToCreateTempFile, err).Error(),
				},
			}, err

		}
		scriptPath = tmp.Name()
		defer func() {
			_ = os.Remove(scriptPath)
		}()

		_, err = tmp.WriteString(s.Script)
		defer func() {
			_ = tmp.Close()
		}()
		if err != nil {
			Error("Failed to write temp file: %v", err)
			return RunResult[ShellResult]{
				Status: ResultStatusFailed,
				Data: ShellResult{
					Output:   "",
					ExitCode: DefaultErrorExitCode,
					Error:    fmt.Errorf("%w: %v", ErrFailedToWriteTempFile, err).Error(),
				},
			}, err
		}

	}

	// #nosec
	cmd := exec.CommandContext(ctx, "sh", scriptPath)
	output, err := cmd.Output()

	var stderr []byte
	if err != nil {
		Error("Failed to run script: %v", err)
		stderr = err.(*exec.ExitError).Stderr
	}

	exitCode := SuccessExitCode

	if err != nil {
		if exitError, ok := err.(*exec.ExitError); ok {
			exitCode = exitError.ExitCode()
		}

		Error("Failed to run script: %v", err)

		return RunResult[ShellResult]{
			Status: ResultStatusFailed,
			Data: ShellResult{
				Output:   string(output),
				ExitCode: exitCode,
				Error:    string(stderr),
			},
		}, err
	}

	return RunResult[ShellResult]{
		Status: ResultStatusSuccess,
		Data: ShellResult{
			Output:   strings.TrimSpace(string(output)),
			ExitCode: exitCode,
			Error:    string(stderr),
		},
	}, nil
}

func (s *ShellRunnable) GetType() string {
	return "shell"
}

func (s *ShellRunnable) GetPersistence() RunnableImport {
	s.mu.Lock()
	defer s.mu.Unlock()

	data := JSONMap{
		"scriptPath": s.ScriptPath,
		"script":     s.Script,
	}

	return RunnableImport{
		Type: s.GetType(),
		Data: data,
	}
}