//go:build windows

package remotevm

import (
	"context"
	"io"
	"net"
	"os/exec"

	"github.com/Microsoft/hcsshim/internal/jobobject"
	"github.com/Microsoft/hcsshim/internal/log"
	"github.com/Microsoft/hcsshim/internal/vm"
	"github.com/Microsoft/hcsshim/internal/vmservice"
	"github.com/containerd/ttrpc"
	ptypes "github.com/gogo/protobuf/types"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

var _ vm.UVMBuilder = &utilityVMBuilder{}

type utilityVMBuilder struct {
	id      string
	guestOS vm.GuestOS
	job     *jobobject.JobObject
	config  *vmservice.VMConfig
	client  vmservice.VMService
}

func NewUVMBuilder(ctx context.Context, id, owner, binPath, addr string, guestOS vm.GuestOS) (vm.UVMBuilder, error) {
	var job *jobobject.JobObject
	if binPath != "" {
		log.G(ctx).WithFields(logrus.Fields{
			"binary":  binPath,
			"address": addr,
		}).Debug("starting remotevm server process")

		opts := &jobobject.Options{
			Name: id,
		}
		job, err := jobobject.Create(ctx, opts)
		if err != nil {
			return nil, errors.Wrap(err, "failed to create job object for remotevm process")
		}

		cmd := exec.Command(binPath, "--ttrpc", addr)
		p, err := cmd.StdoutPipe()
		if err != nil {
			return nil, errors.Wrap(err, "failed to create stdout pipe")
		}

		if err := cmd.Start(); err != nil {
			return nil, errors.Wrap(err, "failed to start remotevm server process")
		}

		if err := job.Assign(uint32(cmd.Process.Pid)); err != nil {
			return nil, errors.Wrap(err, "failed to assign remotevm process to job")
		}

		if err := job.SetTerminateOnLastHandleClose(); err != nil {
			return nil, errors.Wrap(err, "failed to set terminate on last handle closed for remotevm job object")
		}

		// Wait for stdout to close. This is our signal that the server is successfully up and running.
		_, _ = io.Copy(io.Discard, p)
	}

	conn, err := net.Dial("unix", addr)
	if err != nil {
		return nil, errors.Wrapf(err, "failed to dial remotevm address %q", addr)
	}

	c := ttrpc.NewClient(conn, ttrpc.WithOnClose(func() { conn.Close() }))
	vmClient := vmservice.NewVMClient(c)

	return &utilityVMBuilder{
		id:      id,
		guestOS: guestOS,
		config: &vmservice.VMConfig{
			MemoryConfig:    &vmservice.MemoryConfig{},
			DevicesConfig:   &vmservice.DevicesConfig{},
			ProcessorConfig: &vmservice.ProcessorConfig{},
			SerialConfig:    &vmservice.SerialConfig{},
			ExtraData:       make(map[string]string),
		},
		job:    job,
		client: vmClient,
	}, nil
}

func (uvmb *utilityVMBuilder) Create(ctx context.Context) (vm.UVM, error) {
	// Grab what capabilities the virtstack supports up front.
	capabilities, err := uvmb.client.CapabilitiesVM(ctx, &ptypes.Empty{})
	if err != nil {
		return nil, errors.Wrap(err, "failed to get virtstack capabilities from vmservice")
	}

	if _, err := uvmb.client.CreateVM(ctx, &vmservice.CreateVMRequest{Config: uvmb.config, LogID: uvmb.id}); err != nil {
		return nil, errors.Wrap(err, "failed to create remote VM")
	}

	return &utilityVM{
		id:           uvmb.id,
		job:          uvmb.job,
		config:       uvmb.config,
		client:       uvmb.client,
		capabilities: capabilities,
	}, nil
}
