package server

import (
	"context"
	"encoding/json"
	"log"
	"net/http"
	"strings"
	"sync"
	"time"

	"agent/internal/agent"

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

var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 4096,
	// 本地单用户 Web，允许任意 Origin
	CheckOrigin: func(r *http.Request) bool { return true },
}

// wsInbound 客户端 → 服务端消息。
// chat：在绑定会话上运行 Agent；switch：切换绑定会话；stop：停止当前生成；ping：保活。
type wsInbound struct {
	Type      string `json:"type"`
	Content   string `json:"content"`
	SessionID string `json:"session_id"`
}

// wsConn 封装一个 WebSocket 连接：绑定一个会话、串行处理消息。
type wsConn struct {
	server *Server
	conn   *websocket.Conn
	sendMu sync.Mutex // 串行化 WriteJSON

	sessionID string
	unreg     func() // 当前会话的 live 注销函数

	runMu     sync.Mutex
	runCancel context.CancelFunc
}

func (s *Server) ws(c *gin.Context) {
	sessionID := strings.TrimSpace(c.Query("session"))
	if sessionID == "" || !sessionIDPattern.MatchString(sessionID) {
		log.Printf("ws: 连接被拒（session 参数无效）session=%q", sessionID)
		fail(c, http.StatusBadRequest, "缺少有效的 session 参数")
		return
	}
	if _, ok := s.store.Get(sessionID); !ok {
		log.Printf("ws: 连接被拒（会话不存在）session=%q", sessionID)
		fail(c, http.StatusNotFound, "会话不存在")
		return
	}

	conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
	if err != nil {
		return
	}
	client := &wsConn{server: s, conn: conn}
	client.bind(sessionID)
	client.readPump()
}

// sendJSON 安全地推送一个事件帧 {type, data}。
func (cl *wsConn) sendJSON(eventType string, data any) {
	cl.sendMu.Lock()
	defer cl.sendMu.Unlock()
	_ = cl.conn.WriteJSON(map[string]any{"type": eventType, "data": data})
}

// bind 切换绑定会话：注销旧会话的 live 注册，注册新会话。
// 传入空串表示仅注销。
func (cl *wsConn) bind(sessionID string) {
	if cl.unreg != nil {
		cl.unreg()
		cl.unreg = nil
	}
	cl.sessionID = sessionID
	if sessionID == "" {
		return
	}
	cl.unreg = cl.server.agent.RegisterLive(sessionID, func(event agent.Event) {
		cl.sendJSON(event.Type, event.Data)
	})
}

func (cl *wsConn) handleChat(content string) {
	content = strings.TrimSpace(content)
	if content == "" {
		cl.sendJSON("error", map[string]any{"message": "消息内容不能为空"})
		return
	}
	// 未配置 LLM API Key 时直接提示去配置，避免进入无效的模型调用。
	// 环境变量 LLM_API_KEY（托管模式）与设置页 api-config.json 都计入已配置。
	if !cl.server.apiCfg.IsAPIKeyConfigured() {
		cl.sendJSON("error", map[string]any{"message": "尚未配置 LLM API，请先点击左下角设置按钮配置 API Key 与模型"})
		return
	}
	cl.runMu.Lock()
	if cl.runCancel != nil {
		cl.runMu.Unlock()
		cl.sendJSON("error", map[string]any{"message": "Agent 正在执行，请等待完成或先停止"})
		return
	}
	ctx, cancel := context.WithCancel(context.Background())
	cl.runCancel = cancel
	cl.runMu.Unlock()

	done := make(chan struct{})
	go func() {
		defer close(done)
		_ = cl.server.agent.Run(ctx, cl.sessionID, content, func(event agent.Event) {
			cl.sendJSON(event.Type, event.Data)
		})
	}()
	go func() {
		<-done
		cl.runMu.Lock()
		cl.runCancel = nil
		cl.runMu.Unlock()
	}()
}

func (cl *wsConn) handleStop() {
	cl.runMu.Lock()
	if cl.runCancel != nil {
		cl.runCancel()
	}
	cl.runMu.Unlock()
}

func (cl *wsConn) readPump() {
	defer func() {
		cl.bind("") // 注销 live
		_ = cl.conn.Close()
	}()
	cl.conn.SetReadLimit(1 << 20) // 1MB
	_ = cl.conn.SetReadDeadline(time.Now().Add(90 * time.Second))
	cl.conn.SetPongHandler(func(string) error {
		_ = cl.conn.SetReadDeadline(time.Now().Add(90 * time.Second))
		return nil
	})

	for {
		// 任何收到消息（含客户端心跳 {type:"ping"}）都刷新 90s 读超时，
		// 否则浏览器只能发协议层 Pong，应用层 ping 不会触发 SetPongHandler，连接必死在第 90 秒
		_ = cl.conn.SetReadDeadline(time.Now().Add(90 * time.Second))
		_, raw, err := cl.conn.ReadMessage()
		if err != nil {
			return
		}
		var msg wsInbound
		if err := json.Unmarshal(raw, &msg); err != nil {
			continue
		}
		switch msg.Type {
		case "chat":
			cl.handleChat(msg.Content)
		case "stop":
			cl.handleStop()
		case "switch":
			if msg.SessionID != "" && sessionIDPattern.MatchString(msg.SessionID) {
				if _, ok := cl.server.store.Get(msg.SessionID); ok {
					cl.bind(msg.SessionID)
				}
			}
		case "ping":
			cl.sendJSON("pong", map[string]any{"time": time.Now().UnixMilli()})
		default:
			log.Printf("ws: unknown message type %q", msg.Type)
		}
	}
}
