package kvm

import (
	"encoding/json"
	"errors"
	"fmt"
	"net"
	"os"
	"os/exec"
	"sync"
	"time"
)

var (
	vpnCmd     *exec.Cmd
	vpnCmdLock = &sync.Mutex{}
)

var vpnSocketConn net.Conn

var vpnOngoingRequests = make(map[int32]chan *CtrlResponse)

var vpnLock = &sync.Mutex{}

func CallVpnCtrlAction(action string, params map[string]interface{}) (*CtrlResponse, error) {
	vpnLock.Lock()
	defer vpnLock.Unlock()
	ctrlAction := CtrlAction{
		Action: action,
		Seq:    seq,
		Params: params,
	}

	responseChan := make(chan *CtrlResponse)
	vpnOngoingRequests[seq] = responseChan
	seq++

	jsonData, err := json.Marshal(ctrlAction)
	if err != nil {
		delete(vpnOngoingRequests, ctrlAction.Seq)
		return nil, fmt.Errorf("error marshaling ctrl action: %w", err)
	}

	scopedLogger := vpnLogger.With().
		Str("action", ctrlAction.Action).
		Interface("params", ctrlAction.Params).Logger()

	scopedLogger.Debug().Msg("sending vpn ctrl action")

	err = WriteVpnCtrlMessage(jsonData)
	if err != nil {
		delete(vpnOngoingRequests, ctrlAction.Seq)
		return nil, ErrorfL(&scopedLogger, "error writing vpn ctrl message", err)
	}

	select {
	case response := <-responseChan:
		delete(vpnOngoingRequests, seq)
		if response.Error != "" {
			return nil, ErrorfL(
				&scopedLogger,
				"error vpn response: %s",
				errors.New(response.Error),
			)
		}
		return response, nil
	case <-time.After(10 * time.Second):
		close(responseChan)
		delete(vpnOngoingRequests, seq)
		return nil, ErrorfL(&scopedLogger, "timeout waiting for response", nil)
	}
}

func WriteVpnCtrlMessage(message []byte) error {
	if vpnSocketConn == nil {
		return fmt.Errorf("vpn socket not conn ected")
	}
	_, err := vpnSocketConn.Write(message)
	return err
}

var vpnCtrlSocketListener net.Listener

var vpnCtrlClientConnected = make(chan struct{})

func waitVpnCtrlClientConnected() {
	<-vpnCtrlClientConnected
}

func StartVpnSocketServer(socketPath string, handleClient func(net.Conn), isCtrl bool) net.Listener {
	scopedLogger := vpnLogger.With().
		Str("socket_path", socketPath).
		Logger()

	// Remove the socket file if it already exists
	if _, err := os.Stat(socketPath); err == nil {
		if err := os.Remove(socketPath); err != nil {
			scopedLogger.Warn().Err(err).Msg("failed to remove existing socket file")
			os.Exit(1)
		}
	}

	listener, err := net.Listen("unixpacket", socketPath)
	if err != nil {
		scopedLogger.Warn().Err(err).Msg("failed to start server")
		os.Exit(1)
	}

	scopedLogger.Info().Msg("server listening")

	go func() {
		for {
			conn, err := listener.Accept()

			if err != nil {
				scopedLogger.Warn().Err(err).Msg("failed to accept socket")
				continue
			}
			if isCtrl {
				// check if the channel is closed
				select {
				case <-vpnCtrlClientConnected:
					scopedLogger.Debug().Msg("vpn ctrl client reconnected")
				default:
					close(vpnCtrlClientConnected)
					scopedLogger.Debug().Msg("first vpn ctrl socket client connected")
				}
			}

			//conn.Write([]byte("[handleVpnCtrlClient]vpn sock test"))
			go handleClient(conn)
		}
	}()

	return listener
}

func StartVpnCtrlSocketServer() {
	vpnCtrlSocketListener = StartVpnSocketServer("/var/run/kvm_vpn.sock", handleVpnCtrlClient, true)
	vpnLogger.Debug().Msg("vpn ctrl sock started")
}

func handleVpnCtrlClient(conn net.Conn) {
	defer conn.Close()

	scopedLogger := vpnLogger.With().
		Str("addr", conn.RemoteAddr().String()).
		Str("type", "vpn_ctrl").
		Logger()

	scopedLogger.Info().Msg("vpn socket client connected")
	if vpnSocketConn != nil {
		scopedLogger.Debug().Msg("closing existing vpn socket connection")
		vpnSocketConn.Close()
	}

	vpnSocketConn = conn

	readBuf := make([]byte, 4096)
	for {
		n, err := conn.Read(readBuf)
		if err != nil {
			scopedLogger.Warn().Err(err).Msg("error reading from vpn sock")
			break
		}
		readMsg := string(readBuf[:n])

		vpnResp := CtrlResponse{}
		err = json.Unmarshal([]byte(readMsg), &vpnResp)
		if err != nil {
			scopedLogger.Warn().Err(err).Str("data", readMsg).Msg("error parsing vpn sock msg")
			continue
		}
		scopedLogger.Trace().Interface("data", vpnResp).Msg("vpn sock msg")

		if vpnResp.Seq != 0 {
			responseChan, ok := vpnOngoingRequests[vpnResp.Seq]
			if ok {
				responseChan <- &vpnResp
			}
		}
		switch vpnResp.Event {
		case "vpn_display_update":
			HandleVpnDisplayUpdateMessage(vpnResp)
		}
	}

	scopedLogger.Debug().Msg("vpn sock disconnected")
}

func startVpnBinaryWithLock(binaryPath string) (*exec.Cmd, error) {
	vpnCmdLock.Lock()
	defer vpnCmdLock.Unlock()

	cmd, err := startVpnBinary(binaryPath)
	if err != nil {
		return nil, err
	}
	vpnCmd = cmd
	return cmd, nil
}

func restartVpnBinary(binaryPath string) error {
	time.Sleep(10 * time.Second)
	// restart the binary
	vpnLogger.Info().Msg("restarting vpn_video binary")
	cmd, err := startVpnBinary(binaryPath)
	if err != nil {
		vpnLogger.Warn().Err(err).Msg("failed to restart binary")
	}
	vpnCmd = cmd
	return err
}

func superviseVpnBinary(binaryPath string) error {
	vpnCmdLock.Lock()
	defer vpnCmdLock.Unlock()

	if vpnCmd == nil || vpnCmd.Process == nil {
		return restartVpnBinary(binaryPath)
	}

	err := vpnCmd.Wait()

	if err == nil {
		vpnLogger.Info().Err(err).Msg("kvm_vpn binary exited with no error")
	} else if exiterr, ok := err.(*exec.ExitError); ok {
		vpnLogger.Warn().Int("exit_code", exiterr.ExitCode()).Msg("kvm_vpn binary exited with error")
	} else {
		vpnLogger.Warn().Err(err).Msg("kvm_vpn binary exited with unknown error")
	}

	return restartVpnBinary(binaryPath)
}

func ExtractAndRunVpnBin() error {
	binaryPath := "/userdata/picokvm/bin/kvm_vpn"

	// Make the binary executable
	if err := os.Chmod(binaryPath, 0755); err != nil {
		return fmt.Errorf("failed to make binary executable: %w", err)
	}
	// Run the binary in the background
	cmd, err := startVpnBinaryWithLock(binaryPath)
	if err != nil {
		return fmt.Errorf("failed to start binary: %w", err)
	}

	// check if the binary is still running every 10 seconds
	go func() {
		for {
			select {
			case <-appCtx.Done():
				vpnLogger.Info().Msg("stopping vpn binary supervisor")
				return
			default:
				err := superviseVpnBinary(binaryPath)
				if err != nil {
					vpnLogger.Warn().Err(err).Msg("failed to supervise vpn binary")
					time.Sleep(1 * time.Second) // Add a short delay to prevent rapid successive calls
				}
			}
		}
	}()

	go func() {
		<-appCtx.Done()
		vpnLogger.Info().Int("pid", cmd.Process.Pid).Msg("killing process")
		err := cmd.Process.Kill()
		if err != nil {
			vpnLogger.Warn().Err(err).Msg("failed to kill process")
			return
		}
	}()

	vpnLogger.Info().Int("pid", cmd.Process.Pid).Msg("kvm_vpn binary started")

	return nil
}
