//go:build darwin

package vfkit

import (
	"bytes"
	"encoding/json"
	"errors"
	"io"
	"net/http"
	"time"

	"github.com/containers/podman/v5/pkg/machine/define"
	"github.com/crc-org/vfkit/pkg/config"
	rest "github.com/crc-org/vfkit/pkg/rest/define"
	"github.com/sirupsen/logrus"
	"golang.org/x/sys/unix"
)

const (
	inspect = "/vm/inspect"
	state   = "/vm/state"
	version = "/version"
)

func (vf *Helper) get(endpoint string, payload io.Reader) (*http.Response, error) {
	client := &http.Client{}
	req, err := http.NewRequest(http.MethodGet, endpoint, payload)
	if err != nil {
		return nil, err
	}
	return client.Do(req)
}

func (vf *Helper) post(endpoint string, payload io.Reader) (*http.Response, error) {
	client := &http.Client{}
	req, err := http.NewRequest(http.MethodPost, endpoint, payload)
	if err != nil {
		return nil, err
	}
	return client.Do(req)
}

// getRawState asks vfkit for virtual machine state unmodified (see state())
func (vf *Helper) getRawState() (define.Status, error) {
	var response rest.VMState
	endPoint := vf.Endpoint + state
	serverResponse, err := vf.get(endPoint, nil)
	if err != nil {
		if errors.Is(err, unix.ECONNREFUSED) {
			logrus.Debugf("connection refused: %s", endPoint)
		}
		return "", err
	}
	err = json.NewDecoder(serverResponse.Body).Decode(&response)
	if err != nil {
		return "", err
	}
	if err := serverResponse.Body.Close(); err != nil {
		logrus.Error(err)
	}
	return ToMachineStatus(response.State)
}

// state asks vfkit for the virtual machine state. in case the vfkit
// service is not responding, we assume the service is not running
// and return a stopped status
func (vf *Helper) State() (define.Status, error) {
	vmState, err := vf.getRawState()
	if err == nil {
		return vmState, nil
	}
	if errors.Is(err, unix.ECONNREFUSED) {
		return define.Stopped, nil
	}
	return "", err
}

func (vf *Helper) stateChange(newState rest.StateChange) error {
	b, err := json.Marshal(rest.VMState{State: string(newState)})
	if err != nil {
		return err
	}
	payload := bytes.NewReader(b)
	serverResponse, err := vf.post(vf.Endpoint+state, payload)
	if err == nil {
		_ = serverResponse.Body.Close()
	}
	return err
}

func (vf *Helper) Stop(force, wait bool) error {
	state := rest.Stop
	if force {
		state = rest.HardStop
	}
	if err := vf.stateChange(state); err != nil {
		return err
	}
	if !wait {
		return nil
	}
	waitDuration := time.Millisecond * 500
	// Wait up to 90s then hard force off
	for i := 0; i < 180; i++ {
		_, err := vf.getRawState()
		if err != nil {
			//nolint:nilerr // error means vfkit is gone so machine is stopped
			return nil
		}
		time.Sleep(waitDuration)
	}
	logrus.Warn("Failed to gracefully stop machine, performing hard stop")
	// we waited long enough do a hard stop
	return vf.stateChange(rest.HardStop)
}

// Helper describes the use of vfkit: cmdline and endpoint
type Helper struct {
	LogLevel       logrus.Level
	Endpoint       string
	BinaryPath     *define.VMFile
	VirtualMachine *config.VirtualMachine
	Rosetta        bool
}
