package updater

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/dbledeez/panel/agent/internal/runtime"
	"github.com/dbledeez/panel/pkg/steamvdf"
)

// FetchAppInfoBranches runs a one-shot SteamCMD sidecar with
//
//	+login anonymous +app_info_update 1 +app_info_print <appID> +quit
//
// captures its stdout, and parses the depots.branches map (branch name →
// latest buildid). CHECK-ONLY: no game volumes are mounted, so this can
// never touch an install. Only the shared panel-steamcmd-auth volume is
// attached (same as update runs) so Steam's client config cache persists
// across calls.
//
// tag disambiguates the sidecar container name per caller (usually the
// instance id); appID appears too so parallel checks for different apps
// can't collide.
func FetchAppInfoBranches(ctx context.Context, rt runtime.Runtime, tag, appID string, log func(string)) (map[string]string, error) {
	if appID == "" {
		return nil, fmt.Errorf("app_info: app_id is required")
	}
	args := []string{
		"+login", "anonymous",
		"+app_info_update", "1",
		"+app_info_print", appID,
		"+quit",
	}
	spec := runtime.InstanceSpec{
		InstanceID: fmt.Sprintf("%s-appinfo-%s", tag, appID),
		IsSidecar:  true,
		Image:      sidecarImage,
		Command:    args,
		Volumes: []runtime.VolumeSpec{{
			VolumeName:    "panel-steamcmd-auth",
			ContainerPath: "/root/.local/share/Steam",
			Type:          "volume",
		}},
		RestartPolicy: "no",
		// Same seccomp note as the update sidecar (steamcmd.go): newer
		// Docker default profiles ENOSYS a syscall the 32-bit Steam
		// runtime needs.
		SecurityOpts: []string{"seccomp=unconfined"},
	}
	contID, err := rt.Create(ctx, spec)
	if err != nil {
		return nil, fmt.Errorf("create app_info sidecar: %w", err)
	}
	defer func() {
		rmCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
		defer cancel()
		_ = rt.Remove(rmCtx, contID)
	}()
	if err := rt.Start(ctx, contID); err != nil {
		return nil, fmt.Errorf("start app_info sidecar: %w", err)
	}

	var out strings.Builder
	logCtx, cancelLogs := context.WithCancel(ctx)
	defer cancelLogs()
	logDone := make(chan struct{})
	go func() {
		defer close(logDone)
		_ = rt.StreamLogs(logCtx, contID, func(_ runtime.LogStream, line string, _ time.Time) {
			out.WriteString(line)
			out.WriteByte('\n')
		})
	}()
	exitCode, err := rt.Wait(ctx, contID)
	cancelLogs()
	<-logDone
	if err != nil {
		return nil, fmt.Errorf("wait app_info sidecar: %w", err)
	}
	if log != nil {
		log(fmt.Sprintf("app_info: steamcmd exit %d, %d bytes of output", exitCode, out.Len()))
	}
	// SteamCMD sometimes exits non-zero even after printing valid app
	// info — parse first, only surface the exit code when parsing fails.
	branches, perr := steamvdf.ParseAppInfoBranches(out.String(), appID)
	if perr != nil {
		if exitCode != 0 {
			return nil, fmt.Errorf("app_info: steamcmd exit %d (%v)", exitCode, perr)
		}
		return nil, perr
	}
	return branches, nil
}
