package v2

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/1Panel-dev/1Panel/core/app/api/v2/helper"
	"github.com/1Panel-dev/1Panel/core/app/dto"
	"github.com/1Panel-dev/1Panel/core/app/service"
	"github.com/1Panel-dev/1Panel/core/global"
	"github.com/1Panel-dev/1Panel/core/utils/ssh"
	"github.com/1Panel-dev/1Panel/core/utils/terminal"
	"github.com/1Panel-dev/1Panel/core/utils/xpack"
	"github.com/gin-gonic/gin"
	"github.com/gorilla/websocket"
	"github.com/pkg/errors"
)

// @Tags ScriptLibrary
// @Summary Add script
// @Accept json
// @Param request body dto.ScriptOperate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /core/script [post]
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"添加脚本库脚本 [name]","formatEN":"add script [name]"}
func (b *BaseApi) CreateScript(c *gin.Context) {
	var req dto.ScriptOperate
	if err := helper.CheckBindAndValidate(&req, c); err != nil {
		return
	}

	if err := scriptService.Create(req); err != nil {
		helper.InternalServer(c, err)
		return
	}
	helper.Success(c)
}

// @Tags ScriptLibrary
// @Summary Page script
// @Accept json
// @Param request body dto.SearchPageWithGroup true "request"
// @Success 200 {object} dto.PageResult
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /core/script/search [post]
func (b *BaseApi) SearchScript(c *gin.Context) {
	var req dto.SearchPageWithGroup
	if err := helper.CheckBindAndValidate(&req, c); err != nil {
		return
	}

	total, list, err := scriptService.Search(c, req)
	if err != nil {
		helper.InternalServer(c, err)
		return
	}

	helper.SuccessWithData(c, dto.PageResult{
		Items: list,
		Total: total,
	})
}

// @Tags ScriptLibrary
// @Summary Delete script
// @Accept json
// @Param request body dto.OperateByIDs true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /core/script/del [post]
// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"ids","isList":true,"db":"script_librarys","output_column":"name","output_value":"names"}],"formatZH":"删除脚本库脚本 [names]","formatEN":"delete script [names]"}
func (b *BaseApi) DeleteScript(c *gin.Context) {
	var req dto.OperateByIDs
	if err := helper.CheckBindAndValidate(&req, c); err != nil {
		return
	}

	if err := scriptService.Delete(req); err != nil {
		helper.InternalServer(c, err)
		return
	}
	helper.Success(c)
}

// @Tags ScriptLibrary
// @Summary Sync script from remote
// @Accept json
// @Param request body dto.OperateByTaskID true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /core/script/sync [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"同步脚本库脚本","formatEN":"sync scripts"}
func (b *BaseApi) SyncScript(c *gin.Context) {
	var req dto.OperateByTaskID
	if err := helper.CheckBindAndValidate(&req, c); err != nil {
		return
	}
	if err := scriptService.Sync(req); err != nil {
		helper.InternalServer(c, err)
		return
	}
	helper.Success(c)
}

// @Tags ScriptLibrary
// @Summary Update script
// @Accept json
// @Param request body dto.ScriptOperate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /core/script/update [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"cronjobs","output_column":"name","output_value":"name"}],"formatZH":"更新脚本库脚本 [name]","formatEN":"update script [name]"}
func (b *BaseApi) UpdateScript(c *gin.Context) {
	var req dto.ScriptOperate
	if err := helper.CheckBindAndValidate(&req, c); err != nil {
		return
	}

	if err := scriptService.Update(req); err != nil {
		helper.InternalServer(c, err)
		return
	}
	helper.Success(c)
}

// @Tags ScriptLibrary
// @Summary Run script
// @Param cols query integer false "cols"
// @Param rows query integer false "rows"
// @Param script_id query integer false "script_id"
// @Param operateNode query string false "operateNode"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /core/script/run [get]
func (b *BaseApi) RunScript(c *gin.Context) {
	if !websocket.IsWebSocketUpgrade(c.Request) {
		helper.Success(c)
		return
	}
	wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
	if err != nil {
		global.LOG.Errorf("gin context http handler failed, err: %v", err)
		return
	}
	defer wsConn.Close()

	if global.CONF.Base.IsDemo {
		if wshandleError(wsConn, errors.New("   demo server, prohibit this operation!")) {
			return
		}
	}

	cols, err := strconv.Atoi(c.DefaultQuery("cols", "80"))
	if wshandleError(wsConn, errors.WithMessage(err, "invalid param cols in request")) {
		return
	}
	rows, err := strconv.Atoi(c.DefaultQuery("rows", "40"))
	if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) {
		return
	}
	scriptID := c.Query("script_id")
	currentNode := c.Query("operateNode")
	intNum, _ := strconv.Atoi(scriptID)
	if intNum == 0 {
		if wshandleError(wsConn, fmt.Errorf("   no such script %v in library, please check and try again!", scriptID)) {
			return
		}
	}
	scriptItem, err := service.LoadScriptInfo(uint(intNum))
	if wshandleError(wsConn, err) {
		return
	}

	quitChan := make(chan bool, 3)
	if currentNode == "local" {
		slave, err := terminal.NewCommand(scriptItem.Script)
		if wshandleError(wsConn, err) {
			return
		}
		defer slave.Close()

		tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave, true)
		if wshandleError(wsConn, err) {
			return
		}

		quitChan := make(chan bool, 3)
		tty.Start(quitChan)
		go slave.Wait(quitChan)
	} else {
		connInfo, _, err := xpack.MultiNodeProvider.LoadNodeInfo(currentNode)
		if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) {
			return
		}

		fileName := ""
		var translations = make(map[string]string)
		_ = json.Unmarshal([]byte(scriptItem.Name), &translations)
		if name, ok := translations["en"]; ok {
			fileName = strings.ReplaceAll(name, " ", "_")
		} else {
			fileName = strings.ReplaceAll(scriptItem.Name, " ", "_")
		}
		client, err := ssh.NewClient(*connInfo)
		if wshandleError(wsConn, errors.WithMessage(err, "set up the connection failed. Please check the host information")) {
			return
		}
		sudoItem := client.SudoHandleCmd()
		defer func() {
			_, _ = client.Runf("%s rm -rf %s", sudoItem, fileName)
			client.Close()
		}()
		std, err := client.Runf("%s touch %s && %s chmod 777 %s && %s cat > %s <<'MYMARKER'\n%s\nMYMARKER\n", sudoItem, fileName, sudoItem, fileName, sudoItem, fileName, scriptItem.Script)
		if wshandleError(wsConn, errors.WithMessage(err, fmt.Sprintf("touch script file failed, err: %s. Please check and retry", std))) {
			return
		}
		initCmd := fmt.Sprintf("%s bash %s", sudoItem, fileName)

		sws, err := terminal.NewLogicSshWsSession(cols, rows, client.Client, wsConn, initCmd)
		if wshandleError(wsConn, err) {
			return
		}
		defer sws.Close()
		sws.Start(quitChan)
		go sws.Wait(quitChan)
	}

	<-quitChan

	global.LOG.Info("websocket finished")
	global.LOG.Info("websocket finished")
	dt := time.Now().Add(time.Second)
	_ = wsConn.WriteControl(websocket.CloseMessage, nil, dt)
}

var upGrader = websocket.Upgrader{
	ReadBufferSize:  4096,
	WriteBufferSize: 16384,
	CheckOrigin: func(r *http.Request) bool {
		return true
	},
}

func wshandleError(ws *websocket.Conn, err error) bool {
	if err != nil {
		global.LOG.Errorf("handler ws faled:, err: %v", err)
		dt := time.Now().Add(time.Second)
		if ctlerr := ws.WriteControl(websocket.CloseMessage, []byte(err.Error()), dt); ctlerr != nil {
			wsData, marshalErr := json.Marshal(terminal.WsMsg{
				Type: terminal.WsMsgCmd,
				Data: base64.StdEncoding.EncodeToString([]byte(err.Error())),
			})
			if marshalErr != nil {
				_ = ws.WriteMessage(websocket.TextMessage, []byte("{\"type\":\"cmd\",\"data\":\"failed to encoding to json\"}"))
			} else {
				_ = ws.WriteMessage(websocket.TextMessage, wsData)
			}
		}
		return true
	}
	return false
}
