package exec

import (
	"bufio"
	"context"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"sync"
	"time"

	"gitea.hostxtra.co.uk/mrhid6/vantage/agent/internal/grpc/pb"
)

type streamWriter struct {
	mu   sync.Mutex
	seq  uint64
	emit func(seq uint64, data []byte)
}

func (w *streamWriter) Write(p []byte) (int, error) {
	w.mu.Lock()
	defer w.mu.Unlock()
	if w.emit != nil {
		buf := make([]byte, len(p))
		copy(buf, p)
		w.emit(w.seq, buf)
		w.seq++
	}
	return len(p), nil
}

func WorkspacePath(workspaceID string) string {
	return filepath.Join(os.TempDir(), "vantage-run-"+workspaceID)
}

func RunStep(cmd *pb.RunStepCmd, emit func(seq uint64, data []byte)) *pb.StepResult {
	res := &pb.StepResult{CommandId: "", OutputEnv: map[string]string{}}

	dir, err := os.MkdirTemp("", "vantage-step-")
	if err != nil {
		res.ExitCode = 1
		res.Stderr = "create temp dir: " + err.Error()
		return res
	}
	defer os.RemoveAll(dir)

	workDir := ""
	if cmd.WorkspaceId != "" {
		workDir = WorkspacePath(cmd.WorkspaceId)
		if err := os.MkdirAll(workDir, 0700); err != nil {
			res.ExitCode = 1
			res.Stderr = "create workspace: " + err.Error()
			return res
		}
	}

	envFile := filepath.Join(dir, "workflow_env")
	if err := os.WriteFile(envFile, nil, 0600); err != nil {
		res.ExitCode = 1
		res.Stderr = "create env file: " + err.Error()
		return res
	}

	var scriptPath string
	var c *exec.Cmd
	timeout := time.Duration(cmd.TimeoutSeconds) * time.Second
	if timeout <= 0 {
		timeout = 30 * time.Minute
	}
	ctx, cancel := context.WithTimeout(context.Background(), timeout)
	defer cancel()

	switch cmd.Interpreter {
	case "powershell":
		scriptPath = filepath.Join(dir, "step.ps1")
		if err := os.WriteFile(scriptPath, []byte(cmd.Script), 0600); err != nil {
			res.ExitCode = 1
			res.Stderr = err.Error()
			return res
		}
		shell := "pwsh"
		if runtime.GOOS == "windows" {
			if _, err := exec.LookPath("pwsh"); err != nil {
				shell = "powershell.exe"
			}
		}
		c = exec.CommandContext(ctx, shell, "-NoProfile", "-NonInteractive", "-File", scriptPath)
	default:
		scriptPath = filepath.Join(dir, "step.sh")
		if err := os.WriteFile(scriptPath, []byte(cmd.Script), 0700); err != nil {
			res.ExitCode = 1
			res.Stderr = err.Error()
			return res
		}
		c = exec.CommandContext(ctx, "bash", scriptPath)
	}

	if workDir != "" {
		c.Dir = workDir
	}

	c.Env = append(os.Environ(), "WORKFLOW_ENV="+envFile)
	for k, v := range cmd.Env {
		c.Env = append(c.Env, k+"="+v)
	}

	sw := &streamWriter{emit: emit}
	c.Stdout = sw
	c.Stderr = sw
	runErr := c.Run()

	if ctx.Err() == context.DeadlineExceeded {
		res.ExitCode = 124
		res.Stderr = "[vantage] step timed out"
	} else if ee, ok := runErr.(*exec.ExitError); ok {
		res.ExitCode = ee.ExitCode()
	} else if runErr != nil {
		res.ExitCode = 1
		res.Stderr = "[vantage] " + runErr.Error()
	}

	res.OutputEnv = parseEnvFile(envFile)
	return res
}

func parseEnvFile(path string) map[string]string {
	out := map[string]string{}
	f, err := os.Open(path)
	if err != nil {
		return out
	}
	defer f.Close()
	sc := bufio.NewScanner(f)
	sc.Buffer(make([]byte, 0, 64*1024), 1024*1024)
	for sc.Scan() {
		line := sc.Text()
		i := strings.IndexByte(line, '=')
		if i <= 0 {
			continue
		}
		out[line[:i]] = line[i+1:]
	}
	return out
}
