package workloads

import (
	"context"
	"fmt"
	"os/exec"
	"strconv"
	"strings"
	"time"
)

const (
	// MaxLogLines and MaxLogBytes are BOTH enforced, whichever binds first.
	//
	// A line count alone does not bound size: 500 lines of a container printing
	// 4KB JSON blobs is 2MB travelling over the bus. This is the same reasoning
	// that gave workflow logs a per-line cap as well as a per-run one.
	MaxLogLines = 500
	MaxLogBytes = 256 * 1024

	logTimeout = 60 * time.Second
)

// Logs returns a bounded snapshot of a workload's recent output.
//
// There is no follow mode. The browser console already offers a real terminal
// on the same server where `docker logs -f` works properly, with its own
// scrollback and cancellation. A snapshot answers "why did this restart",
// which is the question that sends people to the console in the first place.
func Logs(ctx context.Context, kind, id string, tail int) (string, bool, error) {
	if tail <= 0 || tail > MaxLogLines {
		tail = MaxLogLines
	}

	ctx, cancel := context.WithTimeout(ctx, logTimeout)
	defer cancel()

	var cmd *exec.Cmd
	switch kind {
	case "container":
		cmd = exec.CommandContext(ctx, "docker", "logs",
			"--tail", strconv.Itoa(tail), "--timestamps", id)
	case "unit":
		cmd = exec.CommandContext(ctx, "journalctl", "-u", id,
			"-n", strconv.Itoa(tail), "--no-pager", "--output=short-iso")
	default:
		return "", false, fmt.Errorf("unknown workload kind %q", kind)
	}

	// docker logs writes container stderr to our stderr, so both streams must
	// be captured or half the output silently disappears.
	out, err := cmd.CombinedOutput()
	if err != nil && len(out) == 0 {
		return "", false, fmt.Errorf("read logs for %s: %s", id, errText(err))
	}

	text, truncated := capLog(string(out))
	return text, truncated, nil
}

// capLog enforces both limits, trimming from the FRONT: the most recent lines
// are the ones worth keeping.
func capLog(s string) (string, bool) {
	truncated := false

	lines := strings.Split(s, "\n")
	if len(lines) > MaxLogLines {
		lines = lines[len(lines)-MaxLogLines:]
		truncated = true
	}
	s = strings.Join(lines, "\n")

	if len(s) > MaxLogBytes {
		s = s[len(s)-MaxLogBytes:]
		// Drop the leading partial line left by a byte-wise cut.
		if i := strings.IndexByte(s, '\n'); i >= 0 {
			s = s[i+1:]
		}
		truncated = true
	}

	return s, truncated
}
