package api

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"github.com/gorilla/websocket"
	"github.com/labstack/echo/v4"
	"github.com/sdaduanbilei/agent-team/internal/hub"
	"github.com/sdaduanbilei/agent-team/internal/room"
	"github.com/sdaduanbilei/agent-team/internal/scheduler"
	"github.com/sdaduanbilei/agent-team/internal/skill"
	"github.com/sdaduanbilei/agent-team/internal/store"
	"github.com/sdaduanbilei/agent-team/internal/user"
)

type Server struct {
	e         *echo.Echo
	agentsDir string
	skillsDir string
	roomsDir  string
	usersDir  string
	teamsDir  string
	rooms     map[string]*room.Room
	user      *user.User
	mu        sync.RWMutex
	clients   map[string]map[*websocket.Conn]bool // roomID -> conns
	clientsMu sync.Mutex
	upgrader  websocket.Upgrader
	scheduler *scheduler.Scheduler
	store     *store.Store
}

func New(agentsDir, skillsDir, roomsDir, usersDir, teamsDir string, st *store.Store) *Server {
	s := &Server{
		e:         echo.New(),
		agentsDir: agentsDir,
		skillsDir: skillsDir,
		roomsDir:  roomsDir,
		usersDir:  usersDir,
		teamsDir:  teamsDir,
		rooms:     make(map[string]*room.Room),
		clients:   make(map[string]map[*websocket.Conn]bool),
		upgrader:  websocket.Upgrader{CheckOrigin: func(r *http.Request) bool { return true }},
		store:     st,
	}
	s.loadUser()
	s.loadRooms()
	s.initScheduler()
	s.routes()
	return s
}

func (s *Server) Start(addr string) error {
	go s.scheduler.Start(context.Background())
	return s.e.Start(addr)
}

func (s *Server) initScheduler() {
	s.scheduler = scheduler.New(s.roomsDir)
	s.scheduler.Broadcast = func(roomID, eventType, message, userName string) {
		s.broadcast(roomID, room.Event{
			Type:    room.EvtScheduleRun,
			RoomID:  roomID,
			Content: message,
			Agent:   userName,
		})
	}
	if s.store != nil {
		s.scheduler.Store = &storeAdapter{s.store}
	}
	s.syncSchedulerRooms()
}

// storeAdapter 适配 store.Store 到 scheduler.ScheduleStore 接口
type storeAdapter struct {
	st *store.Store
}

func (a *storeAdapter) GetAllEnabled() ([]scheduler.StoreSchedule, error) {
	list, err := a.st.GetAllEnabled()
	if err != nil {
		return nil, err
	}
	result := make([]scheduler.StoreSchedule, len(list))
	for i, s := range list {
		result[i] = scheduler.StoreSchedule{
			ID: s.ID, RoomID: s.RoomID, Cron: s.Cron, Message: s.Message,
			UserName: s.UserName, Enabled: s.Enabled, Once: s.Once,
			CreatedAt: s.CreatedAt, LastRunAt: s.LastRunAt,
		}
	}
	return result, nil
}

func (a *storeAdapter) UpdateLastRun(id, lastRunAt string) error {
	return a.st.UpdateLastRun(id, lastRunAt)
}

func (a *storeAdapter) DisableSchedule(id string) error {
	return a.st.DisableSchedule(id)
}

func (s *Server) syncSchedulerRooms() {
	s.mu.RLock()
	rooms := make(map[string]scheduler.RoomHandler, len(s.rooms))
	for id, r := range s.rooms {
		rooms[id] = r
	}
	s.mu.RUnlock()
	s.scheduler.SetRooms(rooms)
}

func (s *Server) routes() {
	s.e.Static("/", "web/dist")

	g := s.e.Group("/api")
	g.GET("/rooms", s.listRooms)
	g.POST("/rooms", s.createRoom)
	g.PUT("/rooms/:id/team", s.setRoomTeam)
	g.GET("/rooms/:id/team-doc", s.getRoomTeamDoc)
	g.PUT("/rooms/:id/team-doc", s.saveRoomTeamDoc)
	g.GET("/rooms/:id/agents/:agent/files/:file", s.getRoomAgentFile)
	g.PUT("/rooms/:id/agents/:agent/files/:file", s.saveRoomAgentFile)
	g.GET("/agents", s.listAgents)
	g.GET("/agents/:name/files/:file", s.readAgentFile)
	g.PUT("/agents/:name/files/:file", s.writeAgentFile)
	g.POST("/agents", s.createAgent)
	g.DELETE("/agents/:name", s.deleteAgent)
	g.POST("/hub/install", s.hubInstall)
	g.POST("/hub/install-zip", s.hubInstallZIP)
	g.GET("/teams", s.listTeams)
	g.GET("/teams/:name", s.getTeam)
	g.GET("/teams/:name/agents/:agent/files/*file", s.getTeamAgentFile)
	g.PUT("/teams/:name/agents/:agent/files/*file", s.saveTeamAgentFile)
	g.GET("/teams/:name/knowledge", s.listTeamKnowledge)
	g.GET("/teams/:name/knowledge/:file", s.getTeamKnowledgeFile)
	g.PUT("/teams/:name/knowledge/:file", s.saveTeamKnowledgeFile)
	g.POST("/teams/:name/update", s.updateTeam)
	g.DELETE("/teams/:name", s.deleteTeam)
	g.GET("/skills", s.listSkills)
	g.POST("/skills/install-zip", s.installSkillZIP)

	g.GET("/rooms/:id/schedules", s.listSchedules)
	g.POST("/rooms/:id/schedules", s.createSchedule)
	g.PUT("/rooms/:id/schedules/:sid", s.updateSchedule)
	g.DELETE("/rooms/:id/schedules/:sid", s.deleteSchedule)
	g.POST("/rooms/:id/schedules/:sid/run", s.runScheduleNow)

	g.POST("/rooms/:id/compress-memory", s.compressMemory)
	g.GET("/rooms/:id/workspace", s.listWorkspace)
	g.GET("/rooms/:id/workspace/:file", s.getWorkspaceFile)
	g.GET("/rooms/:id/workspace/:file/versions", s.getFileVersions)
	g.GET("/rooms/:id/workspace/:file/versions/:version", s.getFileVersion)
	g.POST("/rooms/:id/workspace/:file/revert/:version", s.revertFileVersion)
	g.PUT("/rooms/:id/workspace/:file", s.putWorkspaceFile)
	g.GET("/rooms/:id/tasks", s.getTasks)
	g.GET("/rooms/:id/history", s.listHistory)
	g.GET("/rooms/:id/messages", s.getMessages)
	g.GET("/rooms/:id/token-usage", s.getTokenUsage)
	g.GET("/user", s.getUser)
	g.GET("/user/profile", s.getUserProfile)
	g.PUT("/user/profile", s.saveUserProfile)
	g.PUT("/user/config", s.saveUserConfig)

	s.e.GET("/ws/:roomID", s.wsHandler)
}

func (s *Server) loadRooms() {
	entries, _ := os.ReadDir(s.roomsDir)
	for _, e := range entries {
		if !e.IsDir() {
			continue
		}
		r, err := room.Load(filepath.Join(s.roomsDir, e.Name()), s.agentsDir, s.skillsDir, room.WithStore(s.store))
		if err != nil {
			continue
		}
		r.User = s.user
		r.Store = s.store
		r.Broadcast = func(ev room.Event) { s.broadcast(ev.RoomID, ev) }
		s.rooms[e.Name()] = r
	}
}

func (s *Server) loadUser() {
	u, err := user.Load(s.usersDir)
	if err != nil {
		u = &user.User{
			Config: user.Config{
				Name:        "用户",
				Provider:    "deepseek",
				Model:       "deepseek-chat",
				APIKeyEnv:   "DEEPSEEK_API_KEY",
				AvatarColor: "#5865F2",
			},
			Profile: "# 我的简介\n\n## 我是谁\n（介绍你的身份、职业背景）\n\n## 工作风格\n- 喜欢的沟通方式\n- 重视的点\n- 不喜欢的内容\n",
			Dir:     filepath.Join(s.usersDir, user.DefaultUser),
		}
		os.MkdirAll(u.Dir, 0755)
		os.WriteFile(filepath.Join(u.Dir, "USER.md"), []byte("---\nname: 用户\ndescription: \nprovider: deepseek\nmodel: deepseek-chat\napi_key_env: DEEPSEEK_API_KEY\navatar_color: \"#5865F2\"\n---\n"), 0644)
		os.WriteFile(filepath.Join(u.Dir, "PROFILE.md"), []byte(u.Profile), 0644)
	}
	s.user = u
}

func (s *Server) broadcast(roomID string, ev room.Event) {
	s.clientsMu.Lock()
	defer s.clientsMu.Unlock()
	data, _ := json.Marshal(ev)
	for conn := range s.clients[roomID] {
		conn.WriteMessage(websocket.TextMessage, data)
	}
}

func (s *Server) wsHandler(c echo.Context) error {
	roomID := c.Param("roomID")
	conn, err := s.upgrader.Upgrade(c.Response(), c.Request(), nil)
	if err != nil {
		return err
	}
	s.clientsMu.Lock()
	if s.clients[roomID] == nil {
		s.clients[roomID] = make(map[*websocket.Conn]bool)
	}
	s.clients[roomID][conn] = true
	s.clientsMu.Unlock()

	defer func() {
		s.clientsMu.Lock()
		delete(s.clients[roomID], conn)
		s.clientsMu.Unlock()
		conn.Close()
	}()

	for {
		_, msg, err := conn.ReadMessage()
		if err != nil {
			break
		}
		var ev struct {
			Type     string `json:"type"`
			Content  string `json:"content"`
			UserName string `json:"user_name"`
			Mode     string `json:"mode"`
		}
		if json.Unmarshal(msg, &ev) != nil {
			continue
		}
		s.mu.RLock()
		r := s.rooms[roomID]
		s.mu.RUnlock()
		if r == nil {
			continue
		}
		if ev.Type == "stop" {
			r.Stop()
			continue
		}
		if ev.Type == "set_mode" && (ev.Mode == "plan" || ev.Mode == "build") {
			r.Mode = ev.Mode
			s.broadcast(roomID, room.Event{Type: room.EvtModeChange, RoomID: roomID, Mode: ev.Mode})
			continue
		}
		if ev.Type != "user_message" {
			continue
		}
		userName := ev.UserName
		if userName == "" {
			userName = s.user.GetName()
		}
		go func() {
			if err := r.HandleUserMessage(context.Background(), userName, ev.Content); err != nil {
				log.Printf("[room %s] HandleUserMessage error: %v", roomID, err)
			}
		}()
	}
	return nil
}

// --- REST handlers ---

func (s *Server) listRooms(c echo.Context) error {
	s.mu.RLock()
	defer s.mu.RUnlock()
	type roomInfo struct {
		ID      string      `json:"id"`
		Name    string      `json:"name"`
		Type    string      `json:"type"`
		Status  room.Status `json:"status"`
		Master  string      `json:"master"`
		Members []string    `json:"members"`
		Color   string      `json:"color"`
		Team    string      `json:"team"`
		Mode    string      `json:"mode"`
	}
	var list []roomInfo
	for id, r := range s.rooms {
		mode := r.Mode
		if mode == "" {
			mode = "plan"
		}
		list = append(list, roomInfo{ID: id, Name: r.Config.Name, Type: string(r.Config.Type), Status: r.Status, Master: r.Config.Master, Members: r.Config.Members, Color: r.Config.Color, Team: r.Config.Team, Mode: mode})
	}
	return c.JSON(200, list)
}

func (s *Server) createRoom(c echo.Context) error {
	var cfg room.Config
	if err := c.Bind(&cfg); err != nil {
		return err
	}
	if cfg.Type == "" {
		cfg.Type = room.TypeProject
	}
	dir := filepath.Join(s.roomsDir, cfg.Name)
	os.MkdirAll(filepath.Join(dir, "workspace"), 0755)
	os.MkdirAll(filepath.Join(dir, "history"), 0755)

	s.writeRoomFile(dir, cfg)

	r, err := room.Load(dir, s.agentsDir, s.skillsDir, room.WithStore(s.store))
	if err != nil {
		return c.JSON(500, map[string]string{"error": err.Error()})
	}
	r.Broadcast = func(ev room.Event) { s.broadcast(ev.RoomID, ev) }
	r.Store = s.store
	s.mu.Lock()
	s.rooms[cfg.Name] = r
	s.mu.Unlock()
	s.syncSchedulerRooms()
	return c.JSON(201, map[string]string{"id": cfg.Name})
}

func (s *Server) writeRoomFile(dir string, cfg room.Config) {
	content := "---\nname: " + cfg.Name + "\ntype: " + string(cfg.Type) + "\nmaster: " + cfg.Master + "\ncolor: " + cfg.Color + "\nteam: " + cfg.Team + "\nmembers:\n"
	for _, m := range cfg.Members {
		content += "  - " + m + "\n"
	}
	content += "---\n"
	os.WriteFile(filepath.Join(dir, "room.md"), []byte(content), 0644)
}

func (s *Server) setRoomTeam(c echo.Context) error {
	id := c.Param("id")
	var body struct {
		Team string `json:"team"`
	}
	if err := c.Bind(&body); err != nil {
		return err
	}

	// Load team agents
	teamDir := filepath.Join(s.teamsDir, body.Team)
	team, err := loadTeam(filepath.Join(teamDir, "team.yaml"))
	if err != nil {
		return c.JSON(404, map[string]string{"error": "team not found"})
	}

	s.mu.Lock()
	r := s.rooms[id]
	if r == nil {
		s.mu.Unlock()
		return c.JSON(404, map[string]string{"error": "room not found"})
	}

	// Find master and members from team agents by reading AGENT.md
	master := ""
	var members []string
	for _, agentName := range team.Agents {
		agentMD, err := os.ReadFile(filepath.Join(s.agentsDir, body.Team, agentName, "AGENT.md"))
		if err == nil && strings.Contains(string(agentMD), "role: master") {
			master = agentName
		} else {
			members = append(members, agentName)
		}
	}
	if master == "" && len(team.Agents) > 0 {
		master = team.Agents[0]
		members = team.Agents[1:]
	}

	cfg := r.Config
	cfg.Team = body.Team
	cfg.Master = master
	cfg.Members = members

	s.writeRoomFile(r.Dir, cfg)

	// 将 agent 配置（SOUL.md、AGENT.md）和 TEAM.md 写入 SQLite
	if s.store != nil {
		for _, agentName := range team.Agents {
			agentDir := filepath.Join(s.agentsDir, body.Team, agentName)
			if soul, err := os.ReadFile(filepath.Join(agentDir, "SOUL.md")); err == nil {
				s.store.SetAgentConfig(id, agentName, "soul", string(soul))
			}
			if agentMD, err := os.ReadFile(filepath.Join(agentDir, "AGENT.md")); err == nil {
				// 只存正文部分（去掉 frontmatter 后的内容）
				s.store.SetAgentConfig(id, agentName, "agent", string(agentMD))
			}
		}
		if teamMD, err := os.ReadFile(filepath.Join(s.agentsDir, body.Team, "TEAM.md")); err == nil {
			s.store.SetAgentConfig(id, "__team__", "team_doc", string(teamMD))
		}
	}

	newRoom, err := room.Load(r.Dir, s.agentsDir, s.skillsDir, room.WithStore(s.store))
	if err != nil {
		s.mu.Unlock()
		return c.JSON(500, map[string]string{"error": err.Error()})
	}
	newRoom.Broadcast = func(ev room.Event) { s.broadcast(ev.RoomID, ev) }
	newRoom.User = s.user
	newRoom.Store = s.store
	s.rooms[id] = newRoom
	s.mu.Unlock()
	s.syncSchedulerRooms()

	return c.JSON(200, map[string]interface{}{
		"team":    body.Team,
		"master":  master,
		"members": members,
	})
}

func (s *Server) getRoomTeamDoc(c echo.Context) error {
	id := c.Param("id")
	if s.store == nil {
		return c.JSON(200, map[string]string{"content": ""})
	}
	content, err := s.store.GetAgentConfig(id, "__team__", "team_doc")
	if err != nil {
		return c.JSON(200, map[string]string{"content": ""})
	}
	return c.JSON(200, map[string]string{"content": content})
}

func (s *Server) saveRoomTeamDoc(c echo.Context) error {
	id := c.Param("id")
	var body struct {
		Content string `json:"content"`
	}
	if err := c.Bind(&body); err != nil {
		return err
	}
	if s.store == nil {
		return c.JSON(500, map[string]string{"error": "no store"})
	}
	if err := s.store.SetAgentConfig(id, "__team__", "team_doc", body.Content); err != nil {
		return c.JSON(500, map[string]string{"error": err.Error()})
	}
	// 重新加载 room 以应用新模板
	s.reloadRoom(id)
	return c.JSON(200, map[string]string{"status": "ok"})
}

// getRoomAgentFile 读取 room 维度的 agent 配置文件（优先 SQLite，降级文件系统）
func (s *Server) getRoomAgentFile(c echo.Context) error {
	id := c.Param("id")
	agentName := c.Param("agent")
	fileType := c.Param("file") // "soul" | "agent"

	// 优先从 SQLite 读取
	if s.store != nil {
		content, err := s.store.GetAgentConfig(id, agentName, fileType)
		if err == nil && content != "" {
			return c.JSON(200, map[string]string{"content": content})
		}
	}

	// 降级：从文件系统读取
	s.mu.RLock()
	r := s.rooms[id]
	s.mu.RUnlock()
	if r != nil && r.Config.Team != "" {
		var filename string
		switch fileType {
		case "soul":
			filename = "SOUL.md"
		case "agent":
			filename = "AGENT.md"
		}
		if filename != "" {
			fpath := filepath.Join(s.agentsDir, r.Config.Team, agentName, filename)
			if data, err := os.ReadFile(fpath); err == nil {
				content := string(data)
				// 顺便写入 DB，下次就不用再读文件了
				if s.store != nil {
					s.store.SetAgentConfig(id, agentName, fileType, content)
				}
				return c.JSON(200, map[string]string{"content": content})
			}
		}
	}

	return c.JSON(200, map[string]string{"content": ""})
}

// saveRoomAgentFile 保存 room 维度的 agent 配置文件（到 SQLite）
func (s *Server) saveRoomAgentFile(c echo.Context) error {
	id := c.Param("id")
	agentName := c.Param("agent")
	fileType := c.Param("file") // "soul" | "agent"
	var body struct {
		Content string `json:"content"`
	}
	if err := c.Bind(&body); err != nil {
		return err
	}
	if s.store == nil {
		return c.JSON(500, map[string]string{"error": "no store"})
	}
	if err := s.store.SetAgentConfig(id, agentName, fileType, body.Content); err != nil {
		return c.JSON(500, map[string]string{"error": err.Error()})
	}
	// 重新加载 room 以应用新配置
	s.reloadRoom(id)
	return c.JSON(200, map[string]string{"status": "ok"})
}

// reloadRoom 重新加载指定 room
func (s *Server) reloadRoom(id string) {
	s.mu.Lock()
	old := s.rooms[id]
	if old == nil {
		s.mu.Unlock()
		return
	}
	newRoom, err := room.Load(old.Dir, s.agentsDir, s.skillsDir, room.WithStore(s.store))
	if err != nil {
		s.mu.Unlock()
		return
	}
	newRoom.Broadcast = func(ev room.Event) { s.broadcast(ev.RoomID, ev) }
	newRoom.User = s.user
	newRoom.Store = s.store
	s.rooms[id] = newRoom
	s.mu.Unlock()
}

func (s *Server) listAgents(c echo.Context) error {
	entries, _ := os.ReadDir(s.agentsDir)
	type agentInfo struct {
		Name string `json:"name"`
	}
	var list []agentInfo
	for _, e := range entries {
		if e.IsDir() {
			list = append(list, agentInfo{Name: e.Name()})
		}
	}
	return c.JSON(200, list)
}

func (s *Server) readAgentFile(c echo.Context) error {
	name := c.Param("name")
	file := c.Param("file") // AGENT.md or SOUL.md
	data, err := os.ReadFile(filepath.Join(s.agentsDir, name, file))
	if err != nil {
		return c.JSON(404, map[string]string{"error": "not found"})
	}
	return c.JSON(200, map[string]string{"content": string(data)})
}

func (s *Server) writeAgentFile(c echo.Context) error {
	name := c.Param("name")
	file := c.Param("file")
	var body struct {
		Content string `json:"content"`
	}
	if err := c.Bind(&body); err != nil {
		return err
	}
	dir := filepath.Join(s.agentsDir, name)
	os.MkdirAll(dir, 0755)
	return os.WriteFile(filepath.Join(dir, file), []byte(body.Content), 0644)
}

func (s *Server) createAgent(c echo.Context) error {
	var body struct {
		Name string `json:"name"`
	}
	if err := c.Bind(&body); err != nil {
		return err
	}
	dir := filepath.Join(s.agentsDir, body.Name)
	os.MkdirAll(filepath.Join(dir, "memory"), 0755)
	agentMD := "---\nname: " + body.Name + "\ndescription: \nprovider: deepseek\nmodel: deepseek-chat\napi_key_env: DEEPSEEK_API_KEY\nskills: []\n---\n"
	os.WriteFile(filepath.Join(dir, "AGENT.md"), []byte(agentMD), 0644)
	os.WriteFile(filepath.Join(dir, "SOUL.md"), []byte("You are "+body.Name+"."), 0644)
	return c.JSON(201, map[string]string{"name": body.Name})
}

func (s *Server) deleteAgent(c echo.Context) error {
	name := c.Param("name")
	return os.RemoveAll(filepath.Join(s.agentsDir, name))
}

func (s *Server) hubInstall(c echo.Context) error {
	var body struct {
		Repo string `json:"repo"`
	}
	if err := c.Bind(&body); err != nil {
		return err
	}
	team, err := hub.Install(body.Repo, s.agentsDir, s.skillsDir, s.teamsDir)
	if err != nil {
		return c.JSON(500, map[string]string{"error": err.Error()})
	}
	s.loadRooms()
	return c.JSON(200, team)
}

func (s *Server) hubInstallZIP(c echo.Context) error {
	file, err := c.FormFile("file")
	if err != nil {
		return c.JSON(400, map[string]string{"error": "no file uploaded"})
	}
	src, err := file.Open()
	if err != nil {
		return err
	}
	defer src.Close()

	tmpFile := filepath.Join(os.TempDir(), file.Filename)
	dst, err := os.Create(tmpFile)
	if err != nil {
		return err
	}
	defer os.Remove(tmpFile)
	defer dst.Close()

	if _, err := io.Copy(dst, src); err != nil {
		return err
	}

	team, err := hub.InstallZIP(tmpFile, s.agentsDir, s.skillsDir, s.teamsDir)
	if err != nil {
		return c.JSON(500, map[string]string{"error": err.Error()})
	}
	s.loadRooms()
	return c.JSON(200, team)
}

func (s *Server) listWorkspace(c echo.Context) error {
	id := c.Param("id")
	dir := filepath.Join(s.roomsDir, id, "workspace")
	entries, _ := os.ReadDir(dir)
	var files []string
	for _, e := range entries {
		if !e.IsDir() && !strings.HasPrefix(e.Name(), ".") {
			files = append(files, e.Name())
		}
	}
	return c.JSON(200, files)
}

func (s *Server) getTasks(c echo.Context) error {
	id := c.Param("id")
	data, _ := os.ReadFile(filepath.Join(s.roomsDir, id, "tasks.md"))
	return c.JSON(200, map[string]string{"content": string(data)})
}

func (s *Server) listHistory(c echo.Context) error {
	id := c.Param("id")
	entries, _ := os.ReadDir(filepath.Join(s.roomsDir, id, "history"))
	var files []string
	for _, e := range entries {
		files = append(files, e.Name())
	}
	return c.JSON(200, files)
}

func (s *Server) createSkill(c echo.Context) error {
	var body struct {
		Name    string `json:"name"`
		Content string `json:"content"`
	}
	if err := c.Bind(&body); err != nil {
		return err
	}
	dir := filepath.Join(s.skillsDir, body.Name)
	os.MkdirAll(dir, 0755)
	content := body.Content
	if content == "" {
		content = "---\nname: " + body.Name + "\ndescription: \n---\n\n# " + body.Name + "\n\n描述这个 skill 的用途和使用步骤。\n"
	}
	return os.WriteFile(filepath.Join(dir, "SKILL.md"), []byte(content), 0644)
}

func (s *Server) listSkills(c echo.Context) error {
	metas, _ := skill.Discover(s.skillsDir)
	type skillInfo struct {
		Name        string `json:"name"`
		Description string `json:"description"`
	}
	var result []skillInfo
	for _, m := range metas {
		result = append(result, skillInfo{Name: m.Name, Description: m.Description})
	}
	return c.JSON(200, result)
}

func (s *Server) installSkillZIP(c echo.Context) error {
	file, err := c.FormFile("file")
	if err != nil {
		return c.JSON(400, map[string]string{"error": "未上传文件"})
	}
	src, err := file.Open()
	if err != nil {
		return err
	}
	defer src.Close()

	tmpFile := filepath.Join(os.TempDir(), file.Filename)
	dst, err := os.Create(tmpFile)
	if err != nil {
		return err
	}
	defer os.Remove(tmpFile)
	defer dst.Close()

	if _, err := io.Copy(dst, src); err != nil {
		return err
	}
	dst.Close()

	// 解压 zip
	tmp, err := os.MkdirTemp("", "skill-install-*")
	if err != nil {
		return err
	}
	defer os.RemoveAll(tmp)

	if err := hub.ExtractZIP(tmpFile, tmp); err != nil {
		return c.JSON(500, map[string]string{"error": "解压失败: " + err.Error()})
	}

	// 如果 zip 内只有一个有效子目录（忽略 __MACOSX 和隐藏目录），进入该子目录
	entries, _ := os.ReadDir(tmp)
	var validEntries []os.DirEntry
	for _, e := range entries {
		if e.IsDir() && !strings.HasPrefix(e.Name(), ".") && e.Name() != "__MACOSX" {
			validEntries = append(validEntries, e)
		}
	}
	root := tmp
	if len(validEntries) == 1 {
		root = filepath.Join(tmp, validEntries[0].Name())
	}

	// 从 SKILL.md 读取 skill 名称
	skillData, err := os.ReadFile(filepath.Join(root, "SKILL.md"))
	if err != nil {
		// 再尝试在子目录中查找
		for _, e := range validEntries {
			p := filepath.Join(tmp, e.Name(), "SKILL.md")
			if d, err2 := os.ReadFile(p); err2 == nil {
				skillData = d
				root = filepath.Join(tmp, e.Name())
				err = nil
				break
			}
		}
		if err != nil {
			return c.JSON(400, map[string]string{"error": "zip 中未找到 SKILL.md"})
		}
	}
	meta, err := skill.ParseMeta(skillData)
	if err != nil {
		return c.JSON(400, map[string]string{"error": "SKILL.md 格式错误: " + err.Error()})
	}
	skillName := meta.Name
	if skillName == "" {
		// 从 zip 文件名推断
		skillName = strings.TrimSuffix(filepath.Base(file.Filename), ".zip")
	}

	// 复制到 skills 目录
	dstDir := filepath.Join(s.skillsDir, skillName)
	if err := hub.CopyDir(root, dstDir); err != nil {
		return c.JSON(500, map[string]string{"error": "安装失败: " + err.Error()})
	}

	// 如果指定了团队，更新 team.yaml 的 skills 列表
	teamName := c.FormValue("team")
	if teamName != "" {
		teamFile := filepath.Join(s.teamsDir, teamName, "team.yaml")
		if team, err := loadTeam(teamFile); err == nil {
			found := false
			for _, sk := range team.Skills {
				if sk == skillName {
					found = true
					break
				}
			}
			if !found {
				team.Skills = append(team.Skills, skillName)
				team.Save(teamFile)
			}
		}
	}

	return c.JSON(200, map[string]string{"name": skillName, "status": "ok"})
}

func (s *Server) getWorkspaceFile(c echo.Context) error {
	id := c.Param("id")
	file := c.Param("file")
	data, err := os.ReadFile(filepath.Join(s.roomsDir, id, "workspace", file))
	if err != nil {
		return c.JSON(404, map[string]string{"error": "not found"})
	}
	return c.JSON(200, map[string]string{"content": string(data)})
}

func (s *Server) putWorkspaceFile(c echo.Context) error {
	id := c.Param("id")
	file := c.Param("file")
	var body struct {
		Content string `json:"content"`
	}
	if err := c.Bind(&body); err != nil {
		return err
	}
	dir := filepath.Join(s.roomsDir, id, "workspace")
	os.MkdirAll(dir, 0755)
	if err := os.WriteFile(filepath.Join(dir, file), []byte(body.Content), 0644); err != nil {
		return err
	}
	return c.JSON(200, map[string]string{"status": "ok"})
}

func (s *Server) getMessages(c echo.Context) error {
	id := c.Param("id")
	if s.store != nil {
		limit := 200
		offset := 0
		if v := c.QueryParam("limit"); v != "" {
			fmt.Sscanf(v, "%d", &limit)
		}
		if v := c.QueryParam("offset"); v != "" {
			fmt.Sscanf(v, "%d", &offset)
		}
		dbMsgs, err := s.store.GetMessages(id, limit, offset)
		if err != nil {
			return c.JSON(500, map[string]string{"error": err.Error()})
		}
		type msgJSON struct {
			ID        int64  `json:"id"`
			Agent     string `json:"agent"`
			Role      string `json:"role"`
			Content   string `json:"content"`
			Filename  string `json:"filename,omitempty"`
			Title     string `json:"title,omitempty"`
			GroupID   *int64 `json:"group_id,omitempty"`
			CreatedAt string `json:"created_at,omitempty"`
			PartType  string `json:"part_type,omitempty"`
		}
		result := make([]msgJSON, 0, len(dbMsgs))
		for _, m := range dbMsgs {
			result = append(result, msgJSON{
				ID: m.ID, Agent: m.Agent, Role: m.Role, Content: m.Content,
				Filename: m.Filename, Title: m.Title, GroupID: m.GroupID, CreatedAt: m.CreatedAt,
				PartType: m.PartType,
			})
		}
		return c.JSON(200, result)
	}

	// 回退：从 markdown 文件解析（旧逻辑）
	historyFile := filepath.Join(s.roomsDir, id, "history", time.Now().Format("2006-01-02")+".md")
	data, err := os.ReadFile(historyFile)
	if err != nil {
		return c.JSON(200, []interface{}{})
	}
	type msg struct {
		ID      string `json:"id"`
		Agent   string `json:"agent"`
		Role    string `json:"role"`
		Content string `json:"content"`
	}
	var msgs []msg
	blocks := strings.Split(string(data), "\n**[")
	for i, block := range blocks {
		if block == "" {
			continue
		}
		headerEnd := strings.Index(block, "\n\n")
		if headerEnd < 0 {
			continue
		}
		header := block[:headerEnd]
		content := strings.TrimSpace(block[headerEnd+2:])
		bracketEnd := strings.Index(header, "] ")
		if bracketEnd < 0 {
			continue
		}
		rest := header[bracketEnd+2:]
		starIdx := strings.Index(rest, "**")
		if starIdx < 0 {
			continue
		}
		agentName := rest[:starIdx]
		roleStr := strings.TrimSpace(rest[starIdx+2:])
		roleStr = strings.TrimPrefix(roleStr, "(")
		roleStr = strings.TrimSuffix(roleStr, ")")
		if agentName == "" {
			agentName = "unknown"
		}
		if roleStr == "" {
			roleStr = "member"
		}
		msgs = append(msgs, msg{ID: fmt.Sprintf("%d", i), Agent: agentName, Role: roleStr, Content: content})
	}
	return c.JSON(200, msgs)
}

func (s *Server) getTokenUsage(c echo.Context) error {
	id := c.Param("id")
	if s.store == nil {
		return c.JSON(200, map[string]int{"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0})
	}
	stats, err := s.store.GetRoomTokenStats(id)
	if err != nil {
		return c.JSON(500, map[string]string{"error": err.Error()})
	}
	return c.JSON(200, map[string]int{
		"prompt_tokens":     stats.PromptTokens,
		"completion_tokens": stats.CompletionTokens,
		"total_tokens":      stats.TotalTokens,
	})
}

func (s *Server) getUser(c echo.Context) error {
	if s.user == nil {
		return c.JSON(404, map[string]string{"error": "user not found"})
	}
	return c.JSON(200, map[string]interface{}{
		"name":         s.user.GetName(),
		"provider":     s.user.GetProvider(),
		"model":        s.user.GetModel(),
		"api_key_env":  s.user.GetAPIKeyEnv(),
		"avatar_color": s.user.GetAvatarColor(),
		"description":  s.user.Config.Description,
		"has_profile":  s.user.Profile != "",
	})
}

func (s *Server) getUserProfile(c echo.Context) error {
	if s.user == nil {
		return c.JSON(404, map[string]string{"error": "user not found"})
	}
	return c.JSON(200, map[string]string{"content": s.user.Profile})
}

func (s *Server) saveUserProfile(c echo.Context) error {
	if s.user == nil {
		return c.JSON(404, map[string]string{"error": "user not found"})
	}
	var body struct {
		Content string `json:"content"`
	}
	if err := c.Bind(&body); err != nil {
		return err
	}
	if err := s.user.SaveProfile(body.Content); err != nil {
		return err
	}
	return c.JSON(200, map[string]string{"status": "ok"})
}

func (s *Server) saveUserConfig(c echo.Context) error {
	if s.user == nil {
		return c.JSON(404, map[string]string{"error": "user not found"})
	}
	var body struct {
		Name        string `json:"name"`
		Description string `json:"description"`
		Provider    string `json:"provider"`
		Model       string `json:"model"`
		APIKeyEnv   string `json:"api_key_env"`
		AvatarColor string `json:"avatar_color"`
	}
	if err := c.Bind(&body); err != nil {
		return err
	}
	cfg := s.user.Config
	if body.Name != "" {
		cfg.Name = body.Name
	}
	if body.Description != "" {
		cfg.Description = body.Description
	}
	if body.Provider != "" {
		cfg.Provider = body.Provider
	}
	if body.Model != "" {
		cfg.Model = body.Model
	}
	if body.APIKeyEnv != "" {
		cfg.APIKeyEnv = body.APIKeyEnv
	}
	if body.AvatarColor != "" {
		cfg.AvatarColor = body.AvatarColor
	}
	if err := s.user.SaveConfig(cfg); err != nil {
		return err
	}
	return c.JSON(200, map[string]string{"status": "ok"})
}

// --- Teams API ---

func (s *Server) listTeams(c echo.Context) error {
	entries, err := os.ReadDir(s.teamsDir)
	if err != nil {
		return c.JSON(200, []interface{}{})
	}
	var teams []interface{}
	for _, e := range entries {
		if !e.IsDir() {
			continue
		}
		team, err := loadTeam(filepath.Join(s.teamsDir, e.Name(), "team.yaml"))
		if err != nil {
			continue
		}
		teams = append(teams, map[string]interface{}{
			"id":           e.Name(),
			"name":         team.Name,
			"description":  team.Description,
			"author":       team.Author,
			"repo_url":     team.RepoURL,
			"agents":       team.Agents,
			"skills":       team.Skills,
			"installed_at": team.InstalledAt,
			"updated_at":   team.UpdatedAt,
		})
	}
	return c.JSON(200, teams)
}

func (s *Server) getTeam(c echo.Context) error {
	name := c.Param("name")
	team, err := loadTeam(filepath.Join(s.teamsDir, name, "team.yaml"))
	if err != nil {
		return c.JSON(404, map[string]string{"error": "team not found"})
	}
	return c.JSON(200, map[string]interface{}{
		"name":         team.Name,
		"description":  team.Description,
		"author":       team.Author,
		"repo_url":     team.RepoURL,
		"agents":       team.Agents,
		"skills":       team.Skills,
		"installed_at": team.InstalledAt,
		"updated_at":   team.UpdatedAt,
	})
}

func (s *Server) getTeamAgentFile(c echo.Context) error {
	teamName := c.Param("name")
	agentName, _ := url.PathUnescape(c.Param("agent"))
	fileName := strings.TrimPrefix(c.Param("*"), "/")
	path := filepath.Join(s.agentsDir, teamName, agentName, fileName)
	data, err := os.ReadFile(path)
	if err != nil {
		return c.JSON(404, map[string]string{"error": "file not found"})
	}
	return c.JSON(200, map[string]string{"content": string(data)})
}

func (s *Server) saveTeamAgentFile(c echo.Context) error {
	teamName := c.Param("name")
	agentName, _ := url.PathUnescape(c.Param("agent"))
	fileName := strings.TrimPrefix(c.Param("*"), "/")
	path := filepath.Join(s.agentsDir, teamName, agentName, fileName)
	var body struct {
		Content string `json:"content"`
	}
	if err := c.Bind(&body); err != nil {
		return err
	}
	os.MkdirAll(filepath.Dir(path), 0755)
	if err := os.WriteFile(path, []byte(body.Content), 0644); err != nil {
		return err
	}
	return c.JSON(200, map[string]string{"status": "ok"})
}

func (s *Server) listTeamKnowledge(c echo.Context) error {
	name := c.Param("name")
	dir := filepath.Join(s.agentsDir, name, "knowledge")
	entries, err := os.ReadDir(dir)
	if err != nil {
		return c.JSON(200, []string{})
	}
	var files []string
	for _, e := range entries {
		if !e.IsDir() && !strings.HasPrefix(e.Name(), ".") {
			files = append(files, e.Name())
		}
	}
	return c.JSON(200, files)
}

func (s *Server) getTeamKnowledgeFile(c echo.Context) error {
	name := c.Param("name")
	fileName := c.Param("file")
	path := filepath.Join(s.agentsDir, name, "knowledge", fileName)
	data, err := os.ReadFile(path)
	if err != nil {
		return c.JSON(404, map[string]string{"error": "file not found"})
	}
	return c.JSON(200, map[string]string{"content": string(data)})
}

func (s *Server) saveTeamKnowledgeFile(c echo.Context) error {
	name := c.Param("name")
	fileName := c.Param("file")
	path := filepath.Join(s.agentsDir, name, "knowledge", fileName)
	var body struct {
		Content string `json:"content"`
	}
	if err := c.Bind(&body); err != nil {
		return err
	}
	os.MkdirAll(filepath.Dir(path), 0755)
	if err := os.WriteFile(path, []byte(body.Content), 0644); err != nil {
		return err
	}
	return c.JSON(200, map[string]string{"status": "ok"})
}

func (s *Server) updateTeam(c echo.Context) error {
	name := c.Param("name")
	team, err := hub.Update(name, s.agentsDir, s.skillsDir, s.teamsDir)
	if err != nil {
		return c.JSON(400, map[string]string{"error": err.Error()})
	}

	// 重新加载使用该团队的 rooms
	s.mu.Lock()
	for id, r := range s.rooms {
		if r.Config.Team == name {
			reloaded, err := room.Load(r.Dir, s.agentsDir, s.skillsDir, room.WithStore(s.store))
			if err != nil {
				log.Printf("[update] 重新加载 room %s 失败: %v", id, err)
				continue
			}
			reloaded.Broadcast = func(ev room.Event) { s.broadcast(ev.RoomID, ev) }
			reloaded.User = s.user
			reloaded.Store = s.store
			s.rooms[id] = reloaded
		}
	}
	s.mu.Unlock()
	s.syncSchedulerRooms()

	return c.JSON(200, map[string]interface{}{
		"name":        team.Name,
		"description": team.Description,
		"updated_at":  team.UpdatedAt,
		"agents":      team.Agents,
		"skills":      team.Skills,
	})
}

func (s *Server) deleteTeam(c echo.Context) error {
	name := c.Param("name")
	// Delete team directory
	if err := os.RemoveAll(filepath.Join(s.teamsDir, name)); err != nil {
		return err
	}
	// Delete agents
	if err := os.RemoveAll(filepath.Join(s.agentsDir, name)); err != nil {
		// Continue even if error
	}
	return c.JSON(200, map[string]string{"status": "ok"})
}

func (s *Server) compressMemory(c echo.Context) error {
	id := c.Param("id")
	s.mu.RLock()
	r := s.rooms[id]
	s.mu.RUnlock()
	if r == nil {
		return c.JSON(404, map[string]string{"error": "room not found"})
	}
	go r.CompressAllMemory(context.Background())
	return c.JSON(200, map[string]string{"status": "compressing"})
}

// --- Schedule API ---

func (s *Server) listSchedules(c echo.Context) error {
	id := c.Param("id")
	if s.store != nil {
		list, err := s.store.ListSchedules(id)
		if err != nil {
			return c.JSON(500, map[string]string{"error": err.Error()})
		}
		if list == nil {
			list = []store.Schedule{}
		}
		return c.JSON(200, list)
	}
	schedules, err := s.scheduler.LoadSchedules(id)
	if err != nil {
		return c.JSON(500, map[string]string{"error": err.Error()})
	}
	if schedules == nil {
		schedules = []scheduler.Schedule{}
	}
	return c.JSON(200, schedules)
}

func (s *Server) createSchedule(c echo.Context) error {
	id := c.Param("id")
	if s.store != nil {
		var sch store.Schedule
		if err := c.Bind(&sch); err != nil {
			return c.JSON(400, map[string]string{"error": err.Error()})
		}
		sch.ID = fmt.Sprintf("%d", time.Now().UnixNano())
		sch.RoomID = id
		sch.Enabled = true
		sch.CreatedAt = time.Now().Format(time.RFC3339)
		if sch.UserName == "" {
			sch.UserName = "scheduler"
		}
		// 验证 cron
		if !sch.Once {
			if _, err := scheduler.ParseCron(sch.Cron); err != nil {
				return c.JSON(400, map[string]string{"error": "无效的 cron 表达式: " + err.Error()})
			}
		}
		if err := s.store.InsertSchedule(&sch); err != nil {
			return c.JSON(400, map[string]string{"error": err.Error()})
		}
		return c.JSON(201, sch)
	}
	var sch scheduler.Schedule
	if err := c.Bind(&sch); err != nil {
		return c.JSON(400, map[string]string{"error": err.Error()})
	}
	sch.ID = fmt.Sprintf("%d", time.Now().UnixNano())
	sch.RoomID = id
	sch.Enabled = true
	if err := s.scheduler.AddSchedule(id, sch); err != nil {
		return c.JSON(400, map[string]string{"error": err.Error()})
	}
	return c.JSON(201, sch)
}

func (s *Server) updateSchedule(c echo.Context) error {
	id := c.Param("id")
	sid := c.Param("sid")
	if s.store != nil {
		var sch store.Schedule
		if err := c.Bind(&sch); err != nil {
			return c.JSON(400, map[string]string{"error": err.Error()})
		}
		sch.ID = sid
		sch.RoomID = id
		if sch.UserName == "" {
			sch.UserName = "scheduler"
		}
		if !sch.Once {
			if _, err := scheduler.ParseCron(sch.Cron); err != nil {
				return c.JSON(400, map[string]string{"error": "无效的 cron 表达式: " + err.Error()})
			}
		}
		if err := s.store.UpdateSchedule(&sch); err != nil {
			return c.JSON(400, map[string]string{"error": err.Error()})
		}
		return c.JSON(200, map[string]string{"status": "ok"})
	}
	var sch scheduler.Schedule
	if err := c.Bind(&sch); err != nil {
		return c.JSON(400, map[string]string{"error": err.Error()})
	}
	if err := s.scheduler.UpdateSchedule(id, sid, sch); err != nil {
		return c.JSON(400, map[string]string{"error": err.Error()})
	}
	return c.JSON(200, map[string]string{"status": "ok"})
}

func (s *Server) deleteSchedule(c echo.Context) error {
	id := c.Param("id")
	sid := c.Param("sid")
	if s.store != nil {
		if err := s.store.DeleteSchedule(id, sid); err != nil {
			return c.JSON(404, map[string]string{"error": err.Error()})
		}
		return c.JSON(200, map[string]string{"status": "ok"})
	}
	if err := s.scheduler.DeleteSchedule(id, sid); err != nil {
		return c.JSON(404, map[string]string{"error": err.Error()})
	}
	return c.JSON(200, map[string]string{"status": "ok"})
}

func (s *Server) runScheduleNow(c echo.Context) error {
	id := c.Param("id")
	sid := c.Param("sid")
	if err := s.scheduler.RunNow(context.Background(), id, sid); err != nil {
		return c.JSON(400, map[string]string{"error": err.Error()})
	}
	return c.JSON(200, map[string]string{"status": "triggered"})
}

func (s *Server) getFileVersions(c echo.Context) error {
	id := c.Param("id")
	file := c.Param("file")
	if s.store == nil {
		return c.JSON(200, []interface{}{})
	}
	versions, err := s.store.GetFileVersions(id, file)
	if err != nil {
		return c.JSON(500, map[string]string{"error": err.Error()})
	}
	type versionJSON struct {
		Version   int    `json:"version"`
		Agent     string `json:"agent"`
		CreatedAt string `json:"created_at"`
	}
	result := make([]versionJSON, 0, len(versions))
	for _, v := range versions {
		result = append(result, versionJSON{
			Version: v.Version, Agent: v.Agent, CreatedAt: v.CreatedAt,
		})
	}
	return c.JSON(200, result)
}

func (s *Server) getFileVersion(c echo.Context) error {
	id := c.Param("id")
	file := c.Param("file")
	versionStr := c.Param("version")
	var version int
	fmt.Sscanf(versionStr, "%d", &version)
	if s.store == nil {
		return c.JSON(404, map[string]string{"error": "no store"})
	}
	v, err := s.store.GetFileVersion(id, file, version)
	if err != nil {
		return c.JSON(404, map[string]string{"error": "version not found"})
	}
	return c.JSON(200, map[string]string{
		"content":    v.Content,
		"agent":      v.Agent,
		"created_at": v.CreatedAt,
	})
}

func (s *Server) revertFileVersion(c echo.Context) error {
	id := c.Param("id")
	file := c.Param("file")
	versionStr := c.Param("version")
	var version int
	fmt.Sscanf(versionStr, "%d", &version)
	if s.store == nil {
		return c.JSON(500, map[string]string{"error": "no store"})
	}
	v, err := s.store.GetFileVersion(id, file, version)
	if err != nil {
		return c.JSON(404, map[string]string{"error": "version not found"})
	}
	// 保存当前版本到 file_versions
	fpath := filepath.Join(s.roomsDir, id, "workspace", file)
	if current, err := os.ReadFile(fpath); err == nil && len(current) > 0 {
		s.store.InsertFileVersion(id, file, string(current), "")
	}
	// 写入回退内容
	dir := filepath.Join(s.roomsDir, id, "workspace")
	os.MkdirAll(dir, 0755)
	if err := os.WriteFile(fpath, []byte(v.Content), 0644); err != nil {
		return c.JSON(500, map[string]string{"error": err.Error()})
	}
	return c.JSON(200, map[string]string{"status": "ok"})
}

func loadTeam(path string) (*hub.Team, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return nil, err
	}
	team := &hub.Team{}
	lines := strings.Split(string(data), "\n")
	var currentField string
	for _, line := range lines {
		if strings.HasPrefix(line, "name: ") {
			team.Name = strings.TrimSpace(strings.TrimPrefix(line, "name: "))
			currentField = ""
		} else if strings.HasPrefix(line, "description: ") {
			team.Description = strings.TrimSpace(strings.TrimPrefix(line, "description: "))
			currentField = ""
		} else if strings.HasPrefix(line, "author: ") {
			team.Author = strings.TrimSpace(strings.TrimPrefix(line, "author: "))
			currentField = ""
		} else if strings.HasPrefix(line, "repo_url: ") {
			team.RepoURL = strings.TrimSpace(strings.TrimPrefix(line, "repo_url: "))
			currentField = ""
		} else if strings.HasPrefix(line, "installed_at: ") {
			team.InstalledAt = strings.TrimSpace(strings.TrimPrefix(line, "installed_at: "))
			currentField = ""
		} else if strings.HasPrefix(line, "updated_at: ") {
			team.UpdatedAt = strings.TrimSpace(strings.TrimPrefix(line, "updated_at: "))
			currentField = ""
		} else if strings.HasPrefix(line, "agents:") {
			currentField = "agents"
		} else if strings.HasPrefix(line, "skills:") {
			currentField = "skills"
		} else if strings.HasPrefix(line, "  - ") {
			item := strings.TrimSpace(strings.TrimPrefix(line, "  - "))
			if currentField == "agents" {
				team.Agents = append(team.Agents, item)
			} else if currentField == "skills" {
				team.Skills = append(team.Skills, item)
			}
		}
	}
	return team, nil
}
