package services

import (
	"encoding/json"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"runtime"
	"sync"

	"github.com/Kodecable/crosspty"
	"github.com/gorilla/websocket"
)

type terminalWSMessage struct {
	Type string `json:"type"`
	Data string `json:"data,omitempty"`
	Cols uint16 `json:"cols,omitempty"`
	Rows uint16 `json:"rows,omitempty"`
}

// ServeWebTerminal attaches a PTY shell session to the websocket connection.
func ServeWebTerminal(conn *websocket.Conn, workDir string) error {
	abs, err := filepath.Abs(workDir)
	if err != nil {
		return fmt.Errorf("解析目录失败: %w", err)
	}
	info, err := os.Stat(abs)
	if err != nil {
		return fmt.Errorf("目录不存在: %s", abs)
	}
	if !info.IsDir() {
		return fmt.Errorf("不是有效目录: %s", abs)
	}

	ptmx, err := crosspty.Start(crosspty.CommandConfig{
		Argv: defaultShellArgv(),
		Dir:  abs,
		Env:  os.Environ(),
		EnvInject: map[string]string{
			"TERM":      "xterm-256color",
			"COLORTERM": "truecolor",
		},
		Size: crosspty.TermSize{Rows: 24, Cols: 80},
	})
	if err != nil {
		if err == crosspty.ErrConPTYNotSupported {
			return fmt.Errorf("当前 Windows 版本不支持 ConPTY，需 Windows 10 1809 或更高版本")
		}
		return fmt.Errorf("启动终端失败: %w", err)
	}
	defer ptmx.Close()

	var writeMu sync.Mutex
	writeJSON := func(v any) error {
		writeMu.Lock()
		defer writeMu.Unlock()
		return conn.WriteJSON(v)
	}

	go func() {
		buf := make([]byte, 4096)
		for {
			n, readErr := ptmx.Read(buf)
			if n > 0 {
				writeMu.Lock()
				werr := conn.WriteMessage(websocket.BinaryMessage, buf[:n])
				writeMu.Unlock()
				if werr != nil {
					return
				}
			}
			if readErr != nil {
				return
			}
		}
	}()

	for {
		msgType, payload, readErr := conn.ReadMessage()
		if readErr != nil {
			if websocket.IsCloseError(readErr, websocket.CloseNormalClosure, websocket.CloseGoingAway) {
				return nil
			}
			if readErr == io.EOF {
				return nil
			}
			return readErr
		}

		switch msgType {
		case websocket.BinaryMessage:
			if _, werr := ptmx.Write(payload); werr != nil {
				return werr
			}
		case websocket.TextMessage:
			var msg terminalWSMessage
			if err := json.Unmarshal(payload, &msg); err != nil {
				if _, werr := ptmx.Write(payload); werr != nil {
					return werr
				}
				continue
			}
			switch msg.Type {
			case "input":
				if _, werr := ptmx.Write([]byte(msg.Data)); werr != nil {
					return werr
				}
			case "resize":
				if msg.Cols > 0 && msg.Rows > 0 {
					_ = ptmx.Resize(crosspty.TermSize{Rows: msg.Rows, Cols: msg.Cols})
				}
			case "ping":
				if err := writeJSON(map[string]string{"type": "pong"}); err != nil {
					return err
				}
			default:
				if msg.Data != "" {
					if _, werr := ptmx.Write([]byte(msg.Data)); werr != nil {
						return werr
					}
				}
			}
		}
	}
}

func defaultShellArgv() []string {
	if runtime.GOOS == "windows" {
		if comspec := os.Getenv("COMSPEC"); comspec != "" {
			return []string{comspec}
		}
		return []string{"cmd.exe"}
	}
	if shell := os.Getenv("SHELL"); shell != "" {
		return []string{shell, "-l"}
	}
	return []string{"/bin/bash", "-l"}
}
