package handlers

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

	"github.com/gin-gonic/gin"
	"sproutclaw-web/internal/config"
	"sproutclaw-web/internal/db"
	"sproutclaw-web/internal/models"
	"sproutclaw-web/internal/rpc"
	"sproutclaw-web/internal/services"
	"sproutclaw-web/internal/utils"
)

// RegisterSessions registers all session management routes.
func RegisterSessions(r *gin.RouterGroup, cfg *config.Config, database *db.DB, piClient *rpc.Client) {
	r.GET("/sessions", handleListSessions(cfg, database))
	r.POST("/new-session", handleNewSession(piClient))
	r.POST("/sessions/history", handleSessionHistory(cfg))
	r.POST("/sessions/delete", handleDeleteSession(cfg, database))
	r.POST("/sessions/pin", handlePinSession(cfg, database))
	r.POST("/sessions/load", handleLoadSession(cfg, piClient))
	r.POST("/sessions/activate", handleActivateSession(cfg, piClient))
	r.POST("/sessions/name", handleNameSession(cfg))
	r.GET("/session-state", handleSessionState(piClient))
}

func handleListSessions(cfg *config.Config, database *db.DB) gin.HandlerFunc {
	return func(c *gin.Context) {
		summaries, err := services.BuildSessionList(cfg.SessionsDir, database)
		if err != nil {
			c.JSON(http.StatusInternalServerError, models.ErrorResponse{Error: err.Error()})
			return
		}
		if summaries == nil {
			summaries = []models.SessionSummary{}
		}
		c.JSON(http.StatusOK, models.SessionsResponse{Sessions: summaries})
	}
}

func handleNewSession(piClient *rpc.Client) gin.HandlerFunc {
	return func(c *gin.Context) {
		resp, err := piClient.SendCmd(models.RPCCommand{Type: "new_session"})
		if err != nil {
			c.JSON(http.StatusInternalServerError, models.ErrorResponse{Error: err.Error()})
			return
		}
		if !resp.Success {
			c.JSON(http.StatusInternalServerError, models.ErrorResponse{Error: resp.Error})
			return
		}
		// merge sessionFile from a follow-up get_state
		out := map[string]any{}
		if len(resp.Data) > 0 {
			_ = jsonUnmarshalInto(resp.Data, &out)
		}
		if state, serr := piClient.SendCmd(models.RPCCommand{Type: "get_state"}); serr == nil && state.Success {
			var sd struct {
				SessionFile string `json:"sessionFile"`
			}
			if jsonUnmarshalInto(state.Data, &sd) == nil && sd.SessionFile != "" {
				out["sessionFile"] = sd.SessionFile
			}
		}
		c.JSON(http.StatusOK, out)
	}
}

func handleSessionHistory(cfg *config.Config) gin.HandlerFunc {
	return func(c *gin.Context) {
		var req models.HistoryRequest
		if err := c.ShouldBindJSON(&req); err != nil {
			c.JSON(http.StatusBadRequest, models.ErrorResponse{Error: err.Error()})
			return
		}
		if !utils.IsPathInsideRoot(cfg.SessionsDir, req.Path) {
			c.JSON(http.StatusForbidden, models.ErrorResponse{Error: "invalid path"})
			return
		}
		lines, err := services.ReadSessionMessages(req.Path)
		if err != nil {
			c.JSON(http.StatusNotFound, models.ErrorResponse{Error: err.Error()})
			return
		}
		c.JSON(http.StatusOK, gin.H{"messages": lines})
	}
}

func handleDeleteSession(cfg *config.Config, database *db.DB) gin.HandlerFunc {
	return func(c *gin.Context) {
		var req models.DeleteSessionRequest
		if err := c.ShouldBindJSON(&req); err != nil {
			c.JSON(http.StatusBadRequest, models.ErrorResponse{Error: err.Error()})
			return
		}
		if !utils.IsPathInsideRoot(cfg.SessionsDir, req.Path) {
			c.JSON(http.StatusForbidden, models.ErrorResponse{Error: "invalid path"})
			return
		}
		if err := os.Remove(req.Path); err != nil && !os.IsNotExist(err) {
			c.JSON(http.StatusInternalServerError, models.ErrorResponse{Error: err.Error()})
			return
		}
		_ = database.SetSessionPinned(req.Path, false)
		c.JSON(http.StatusOK, models.OKResponse{OK: true})
	}
}

func handlePinSession(cfg *config.Config, database *db.DB) gin.HandlerFunc {
	return func(c *gin.Context) {
		var req models.PinSessionRequest
		if err := c.ShouldBindJSON(&req); err != nil {
			c.JSON(http.StatusBadRequest, models.ErrorResponse{Error: err.Error()})
			return
		}
		if !utils.IsPathInsideRoot(cfg.SessionsDir, req.Path) {
			c.JSON(http.StatusForbidden, models.ErrorResponse{Error: "invalid path"})
			return
		}
		if err := database.SetSessionPinned(req.Path, req.Pinned); err != nil {
			c.JSON(http.StatusInternalServerError, models.ErrorResponse{Error: err.Error()})
			return
		}
		c.JSON(http.StatusOK, models.OKResponse{OK: true})
	}
}

func handleLoadSession(cfg *config.Config, piClient *rpc.Client) gin.HandlerFunc {
	return func(c *gin.Context) {
		var req models.LoadSessionRequest
		if err := c.ShouldBindJSON(&req); err != nil {
			c.JSON(http.StatusBadRequest, models.ErrorResponse{Error: err.Error()})
			return
		}
		if !utils.IsPathInsideRoot(cfg.SessionsDir, req.Path) {
			c.JSON(http.StatusForbidden, models.ErrorResponse{Error: "invalid path"})
			return
		}
		sw, err := piClient.SendCmd(models.RPCCommand{
			Type:        "switch_session",
			SessionPath: req.Path,
			CwdOverride: cfg.RepoRoot,
		})
		if err != nil {
			c.JSON(http.StatusInternalServerError, models.ErrorResponse{Error: err.Error()})
			return
		}
		if !sw.Success {
			c.JSON(http.StatusInternalServerError, models.ErrorResponse{Error: sw.Error})
			return
		}
		mr, err := piClient.SendCmd(models.RPCCommand{Type: "get_messages"})
		if err != nil {
			c.JSON(http.StatusInternalServerError, models.ErrorResponse{Error: err.Error()})
			return
		}
		if !mr.Success {
			c.JSON(http.StatusInternalServerError, models.ErrorResponse{Error: mr.Error})
			return
		}
		// mr.Data is { messages: [...] }; forward it plus a session summary
		summary, _ := services.ReadSessionSummaryByPath(req.Path)
		c.JSON(http.StatusOK, gin.H{"messages": extractMessages(mr.Data), "session": summary})
	}
}

func handleActivateSession(cfg *config.Config, piClient *rpc.Client) gin.HandlerFunc {
	return func(c *gin.Context) {
		var req models.ActivateSessionRequest
		if err := c.ShouldBindJSON(&req); err != nil {
			c.JSON(http.StatusBadRequest, models.ErrorResponse{Error: err.Error()})
			return
		}
		if !utils.IsPathInsideRoot(cfg.SessionsDir, req.Path) {
			c.JSON(http.StatusForbidden, models.ErrorResponse{Error: "invalid path"})
			return
		}
		sw, err := piClient.SendCmd(models.RPCCommand{
			Type:        "switch_session",
			SessionPath: req.Path,
			CwdOverride: cfg.RepoRoot,
		})
		if err != nil {
			c.JSON(http.StatusInternalServerError, models.ErrorResponse{Error: err.Error()})
			return
		}
		if !sw.Success {
			c.JSON(http.StatusInternalServerError, models.ErrorResponse{Error: sw.Error})
			return
		}
		state, err := piClient.SendCmd(models.RPCCommand{Type: "get_state"})
		if err != nil {
			c.JSON(http.StatusInternalServerError, models.ErrorResponse{Error: err.Error()})
			return
		}
		c.JSON(http.StatusOK, gin.H{"ok": true, "state": jsonRaw(state.Data)})
	}
}

func handleNameSession(cfg *config.Config) gin.HandlerFunc {
	return func(c *gin.Context) {
		var req models.NameSessionRequest
		if err := c.ShouldBindJSON(&req); err != nil {
			c.JSON(http.StatusBadRequest, models.ErrorResponse{Error: err.Error()})
			return
		}
		if !utils.IsPathInsideRoot(cfg.SessionsDir, req.Path) {
			c.JSON(http.StatusForbidden, models.ErrorResponse{Error: "invalid path"})
			return
		}
		if err := services.AppendSessionName(req.Path, req.Name); err != nil {
			c.JSON(http.StatusInternalServerError, models.ErrorResponse{Error: err.Error()})
			return
		}
		c.JSON(http.StatusOK, models.OKResponse{OK: true})
	}
}

func handleSessionState(piClient *rpc.Client) gin.HandlerFunc {
	return func(c *gin.Context) {
		resp, err := piClient.SendCmd(models.RPCCommand{Type: "get_state"})
		if err != nil {
			c.JSON(http.StatusInternalServerError, models.ErrorResponse{Error: err.Error()})
			return
		}
		if !resp.Success {
			c.JSON(http.StatusInternalServerError, models.ErrorResponse{Error: resp.Error})
			return
		}
		c.JSON(http.StatusOK, jsonRaw(resp.Data))
	}
}

// extractMessages pulls the "messages" array out of a get_messages data payload.
func extractMessages(data json.RawMessage) any {
	if len(data) == 0 {
		return []any{}
	}
	var wrap struct {
		Messages json.RawMessage `json:"messages"`
	}
	if jsonUnmarshalInto(data, &wrap) == nil && len(wrap.Messages) > 0 {
		return jsonRaw(wrap.Messages)
	}
	return jsonRaw(data)
}

func jsonUnmarshalInto(raw json.RawMessage, v any) error {
	if len(raw) == 0 {
		return nil
	}
	return json.Unmarshal(raw, v)
}
