package guac

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"mayfly-go/internal/machine/config"
	"mayfly-go/pkg/errorx"
	"mayfly-go/pkg/logx"
	"net"
	"net/url"
	"strconv"

	"github.com/gorilla/websocket"
)

// creates the tunnel to the remote machine (via guacd)
func DoConnect(query url.Values, parameters map[string]string, username string) (Tunnel, error) {
	conf := NewGuacamoleConfiguration()

	parameters["client-name"] = "mayfly"
	parameters["enable-wallpaper"] = "true"
	parameters["resize-method"] = "display-update"
	parameters["enable-font-smoothing"] = "true"
	parameters["enable-desktop-composition"] = "false"
	parameters["enable-menu-animations"] = "false"
	parameters["disable-bitmap-caching"] = "true"
	parameters["disable-offscreen-caching"] = "true"
	parameters["force-lossless"] = "true" // 无损压缩
	parameters["color-depth"] = "32"      //32 真彩（32位）；24 真彩（24位）；16 低色（16位）；8 256色

	// drive
	parameters["enable-drive"] = "true"
	parameters["drive-name"] = "Filesystem"
	parameters["create-drive-path"] = "true"
	parameters["drive-path"] = fmt.Sprintf("/rdp-file/%s", username)

	conf.Protocol = parameters["scheme"]
	conf.Parameters = parameters
	conf.OptimalScreenWidth = 800
	conf.OptimalScreenHeight = 600

	var err error

	if query.Get("width") != "" {
		conf.OptimalScreenWidth, err = strconv.Atoi(query.Get("width"))
		if err != nil || conf.OptimalScreenWidth == 0 {
			logx.Error("Invalid width")
			conf.OptimalScreenWidth = 800
		}
	}

	if query.Get("height") != "" {
		conf.OptimalScreenHeight, err = strconv.Atoi(query.Get("height"))
		if err != nil || conf.OptimalScreenHeight == 0 {
			logx.Error("Invalid height")
			conf.OptimalScreenHeight = 600
		}
	}

	//conf.ConnectionID = uuid.New().String()

	conf.AudioMimetypes = []string{"audio/L16", "rate=44100", "channels=2"}
	conf.ImageMimetypes = []string{"image/jpeg", "image/png", "image/webp"}

	logx.Debug("Connecting to guacd")

	machineConfig := config.GetMachine()
	if machineConfig.GuacdHost == "" {
		return nil, errorx.NewBiz("请前往'系统配置-机器配置'中配置guacd相关信息")
	}
	guacdAddr := fmt.Sprintf("%v:%v", machineConfig.GuacdHost, machineConfig.GuacdPort)
	addr, err := net.ResolveTCPAddr("tcp", guacdAddr)
	if err != nil {
		logx.Error("error resolving guacd address", err)
		return nil, err
	}

	conn, err := net.DialTCP("tcp", nil, addr)
	if err != nil {
		logx.Error("error while connecting to guacd", err)
		return nil, err
	}

	stream := NewStream(conn, SocketTimeout)

	logx.Debug("Connected to guacd")
	//conf.ConnectionID = uuid.New().String()

	logx.Debugf("Starting handshake with %#v", conf)
	err = stream.Handshake(conf)
	if err != nil {
		return nil, err
	}
	logx.Debug("Socket configured")
	return NewSimpleTunnel(stream), nil
}

func WsToGuacd(ws *websocket.Conn, tunnel Tunnel, guacd io.Writer) {
	for {
		_, data, err := ws.ReadMessage()
		if err != nil {
			logx.Warnf("error reading message from ws: %v", err)
			_ = tunnel.Close()
			return
		}

		if bytes.HasPrefix(data, internalOpcodeIns) {
			// messages starting with the InternalDataOpcode are never sent to guacd
			continue
		}

		if _, err = guacd.Write(data); err != nil {
			logx.Warnf("failed writing to guacd: %v", err)
			return
		}
	}
}

func GuacdToWs(ws *websocket.Conn, tunnel Tunnel, guacd InstructionReader) {
	buf := bytes.NewBuffer(make([]byte, 0, MaxGuacMessage*2))

	for {
		ins, err := guacd.ReadSome()
		if err != nil {
			logx.Warnf("error reading message from guacd: %v", err)
			_ = tunnel.Close()
			return
		}

		if bytes.HasPrefix(ins, internalOpcodeIns) {
			// messages starting with the InternalDataOpcode are never sent to the websocket
			continue
		}
		logx.Debugf("guacd msg: %s", string(ins))
		if _, err = buf.Write(ins); err != nil {
			logx.Warnf("failed to buffer guacd to ws: %v", err)
			return
		}

		// if the buffer has more data in it or we've reached the max buffer size, send the data and reset
		if !guacd.Available() || buf.Len() >= MaxGuacMessage {
			if err = ws.WriteMessage(1, buf.Bytes()); err != nil {
				if errors.Is(err, websocket.ErrCloseSent) {
					return
				}
				logx.Warnf("failed sending message to ws: %v", err)
				return
			}
			buf.Reset()
		}
	}
}
