package api

import (
	"encoding/json"
	"io"
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/gorilla/websocket"

	"oci-portal/internal/service"
)

// consoleHandler 提供网页控制台（串行 / VNC）：创建会话 + WebSocket 数据面。
type consoleHandler struct {
	svc  *service.ConsoleService
	auth *service.AuthService
}

type createConsoleSessionRequest struct {
	Type   string `json:"type"`
	Region string `json:"region"`
}

// create 创建控制台会话（内部清理残留连接并创建 OCI 控制台连接，约 30-90 秒）。
//
// @Summary  创建网页控制台会话
// @Tags     网页控制台
// @Param    id path int true "配置 ID"
// @Param    instanceId path string true "实例 OCID"
// @Param    body body object true "{type: serial|vnc}"
// @Success  201 {object} webConsoleSessionResponse "sessionId 与 type"
// @Security BearerAuth
// @Router   /api/v1/oci-configs/{id}/instances/{instanceId}/console-sessions [post]
func (h *consoleHandler) create(c *gin.Context) {
	id, ok := pathID(c)
	if !ok {
		return
	}
	var req createConsoleSessionRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	sess, err := h.svc.CreateSession(c.Request.Context(), id, c.Param("instanceId"), req.Region, req.Type)
	if err != nil {
		respondError(c, err)
		return
	}
	c.JSON(http.StatusCreated, gin.H{"sessionId": sess.ID, "type": sess.Type})
}

// remove 结束会话并删除云端控制台连接。
//
// @Summary  关闭网页控制台会话
// @Tags     网页控制台
// @Param    sessionId path string true "会话 ID"
// @Success  204 "无内容"
// @Security BearerAuth
// @Router   /api/v1/console-sessions/{sessionId} [delete]
func (h *consoleHandler) remove(c *gin.Context) {
	h.svc.CloseSession(c.Param("sessionId"))
	c.Status(http.StatusNoContent)
}

// wsUpgrader 同源校验交给 token（浏览器 WebSocket 无法自定义头，token 走 query）。
var wsUpgrader = websocket.Upgrader{
	ReadBufferSize:  32 * 1024,
	WriteBufferSize: 32 * 1024,
	CheckOrigin:     func(*http.Request) bool { return true },
}

// ws 是控制台数据面：校验 token 后建立两跳 SSH 隧道，按会话类型分派数据泵。
//
// @Summary  控制台 WebSocket 数据面
// @Tags     网页控制台
// @Param    sessionId path string true "会话 ID"
// @Param    token query string true "JWT(浏览器 WebSocket 无法带头,经 query 传递)"
// @Success  101 "升级为 WebSocket"
// @Router   /api/v1/console-sessions/{sessionId}/ws [get]
func (h *consoleHandler) ws(c *gin.Context) {
	if _, err := h.auth.ParseToken(c.Request.Context(), c.Query("token")); err != nil {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid or expired token"})
		return
	}
	sess := h.svc.Get(c.Param("sessionId"))
	if sess == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "console session not found or expired"})
		return
	}
	wsConn, err := wsUpgrader.Upgrade(c.Writer, c.Request, nil)
	if err != nil {
		return
	}
	defer wsConn.Close()
	sess.MarkUse(true)
	defer sess.MarkUse(false)
	if sess.Type == service.ConsoleTypeSerial {
		h.serveSerial(c, sess, wsConn)
		return
	}
	h.serveVnc(c, sess, wsConn)
}

func (h *consoleHandler) serveVnc(c *gin.Context, sess *service.ConsoleSession, wsConn *websocket.Conn) {
	vnc, err := sess.DialVNC(c.Request.Context())
	if err != nil {
		closeWs(wsConn, "tunnel: "+err.Error())
		return
	}
	defer vnc.Close()
	proxyVnc(wsConn, vnc)
}

// serialQuery 读取 ws 建连时的初始终端尺寸（缺省 80x24）。
func serialQuery(c *gin.Context, key string, def int) int {
	v := 0
	for _, ch := range c.Query(key) {
		if ch < '0' || ch > '9' {
			return def
		}
		v = v*10 + int(ch-'0')
	}
	if v < 1 || v > 1000 {
		return def
	}
	return v
}

func (h *consoleHandler) serveSerial(c *gin.Context, sess *service.ConsoleSession, wsConn *websocket.Conn) {
	term, err := sess.OpenSerial(c.Request.Context(),
		serialQuery(c, "rows", 24), serialQuery(c, "cols", 80))
	if err != nil {
		closeWs(wsConn, "tunnel: "+err.Error())
		return
	}
	defer term.Close()
	proxySerial(wsConn, term)
}

func closeWs(wsConn *websocket.Conn, reason string) {
	// CloseMessage 载荷上限 125 字节，超长原因截断避免发送失败
	if len(reason) > 120 {
		reason = reason[:120]
	}
	_ = wsConn.WriteMessage(websocket.CloseMessage,
		websocket.FormatCloseMessage(websocket.ClosePolicyViolation, reason))
}

// serialControl 是串行会话的文本帧控制消息（二进制帧为终端原始字节）。
type serialControl struct {
	Type string `json:"type"`
	Cols int    `json:"cols"`
	Rows int    `json:"rows"`
}

// proxySerial 串行数据泵：二进制帧 ↔ 终端字节流，文本帧解析 resize 控制消息；
// ws 写只发生在本 goroutine，无并发写。
func proxySerial(wsConn *websocket.Conn, term *service.SerialTerminal) {
	go func() {
		defer term.Close()
		for {
			t, data, err := wsConn.ReadMessage()
			if err != nil {
				return
			}
			switch t {
			case websocket.BinaryMessage:
				if _, err := term.Write(data); err != nil {
					return
				}
			case websocket.TextMessage:
				var ctl serialControl
				if json.Unmarshal(data, &ctl) == nil && ctl.Type == "resize" {
					_ = term.Resize(ctl.Rows, ctl.Cols)
				}
			}
		}
	}()
	pumpToWs(wsConn, term)
}

// proxyVnc VNC 数据泵：双向透传 RFB 字节流直到任一侧断开。
func proxyVnc(wsConn *websocket.Conn, vnc io.ReadWriteCloser) {
	go func() {
		defer vnc.Close()
		for {
			t, data, err := wsConn.ReadMessage()
			if err != nil {
				return
			}
			if t == websocket.BinaryMessage || t == websocket.TextMessage {
				if _, err := vnc.Write(data); err != nil {
					return
				}
			}
		}
	}()
	pumpToWs(wsConn, vnc)
}

// pumpToWs 把数据源持续泵入 ws 二进制帧，直到任一侧断开。
func pumpToWs(wsConn *websocket.Conn, src io.Reader) {
	buf := make([]byte, 32*1024)
	for {
		n, err := src.Read(buf)
		if n > 0 {
			if werr := wsConn.WriteMessage(websocket.BinaryMessage, buf[:n]); werr != nil {
				return
			}
		}
		if err != nil {
			return
		}
	}
}
