package projects

import (
	"errors"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"gorm.io/gorm"
	"senlinai-agent/backend/internal/httpx"
	"senlinai-agent/backend/internal/logic/auth"
	"senlinai-agent/backend/internal/logic/files"
)

type Handler struct {
	service     *Service
	fileService *files.Service
}

func NewHandler(service *Service, fileServices ...*files.Service) *Handler {
	var fileService *files.Service
	if len(fileServices) > 0 {
		fileService = fileServices[0]
	}
	return &Handler{service: service, fileService: fileService}
}

func (h *Handler) Register(router gin.IRouter) {
	router.POST("/projects", h.createProject)
	router.GET("/projects", h.listProjects)
	router.GET("/projects/:id", h.getProject)
	router.PATCH("/projects/:id", h.updateProject)
	router.GET("/projects/:id/dashboard", h.dashboard)
	router.GET("/projects/:id/workspace", h.workspace)
	router.GET("/projects/:id/tags", h.listTags)
	router.POST("/projects/:id/tasks", h.createTask)
	router.PATCH("/projects/:id/tasks/:taskID", h.updateTask)
	router.POST("/projects/:id/sources", h.uploadSource)
	router.POST("/projects/:id/cron-plans", h.createCronPlan)
	router.POST("/projects/:id/tags", h.createTag)
}

func (h *Handler) createProject(c *gin.Context) {
	userID, ok := auth.CurrentUserID(c)
	if !ok {
		httpx.Error(c, http.StatusUnauthorized, "unauthorized", "未登录或登录已失效")
		return
	}
	var input CreateProjectRequest
	if err := c.ShouldBindJSON(&input); err != nil {
		httpx.Error(c, http.StatusBadRequest, "invalid_request", "请求参数无效")
		return
	}
	project, err := h.service.CreateProjectWithInput(userID, input)
	if err != nil {
		writeProjectError(c, err)
		return
	}
	c.JSON(http.StatusCreated, projectDTO(*project))
}

func (h *Handler) listProjects(c *gin.Context) {
	userID, ok := auth.CurrentUserID(c)
	if !ok {
		httpx.Error(c, http.StatusUnauthorized, "unauthorized", "未登录或登录已失效")
		return
	}
	projects, err := h.service.ListProjects(userID)
	if err != nil {
		httpx.Error(c, http.StatusInternalServerError, "internal_error", "项目列表加载失败")
		return
	}
	c.JSON(http.StatusOK, projects)
}

func (h *Handler) getProject(c *gin.Context) {
	userID, ok := auth.CurrentUserID(c)
	if !ok {
		httpx.Error(c, http.StatusUnauthorized, "unauthorized", "未登录或登录已失效")
		return
	}
	identity, ok := httpx.IdentityParam(c, "id")
	if !ok {
		return
	}
	project, err := h.service.GetProject(userID, identity)
	if err != nil {
		writeProjectError(c, err)
		return
	}
	c.JSON(http.StatusOK, project)
}

func (h *Handler) updateProject(c *gin.Context) {
	userID, ok := auth.CurrentUserID(c)
	if !ok {
		httpx.Error(c, http.StatusUnauthorized, "unauthorized", "未登录或登录已失效")
		return
	}
	identity, ok := httpx.IdentityParam(c, "id")
	if !ok {
		return
	}
	var input UpdateProjectRequest
	if err := c.ShouldBindJSON(&input); err != nil {
		httpx.Error(c, http.StatusBadRequest, "invalid_request", "请求参数无效")
		return
	}
	if err := h.service.UpdateProject(userID, identity, input); err != nil {
		writeProjectError(c, err)
		return
	}
	c.Status(http.StatusNoContent)
}

func (h *Handler) dashboard(c *gin.Context) {
	userID, ok := auth.CurrentUserID(c)
	if !ok {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "missing current user"})
		return
	}
	projectID, err := strconv.ParseUint(c.Param("id"), 10, 64)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid project id"})
		return
	}
	dashboard, err := h.service.Dashboard(userID, uint(projectID))
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to load dashboard"})
		return
	}
	c.JSON(http.StatusOK, dashboard)
}

func (h *Handler) workspace(c *gin.Context) {
	userID, ok := auth.CurrentUserID(c)
	if !ok {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "missing current user"})
		return
	}
	projectID, err := strconv.ParseUint(c.Param("id"), 10, 64)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid project id"})
		return
	}
	workspace, err := h.service.Workspace(userID, uint(projectID))
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to load workspace"})
		return
	}
	c.JSON(http.StatusOK, workspace)
}

func (h *Handler) listTags(c *gin.Context) {
	userID, ok := auth.CurrentUserID(c)
	if !ok {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "missing current user"})
		return
	}
	projectID, ok := parseProjectID(c)
	if !ok {
		return
	}
	tags, err := h.service.ListProjectTags(userID, projectID)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, tags)
}

func (h *Handler) createTask(c *gin.Context) {
	userID, ok := auth.CurrentUserID(c)
	if !ok {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "missing current user"})
		return
	}
	projectID, ok := parseProjectID(c)
	if !ok {
		return
	}
	var input struct {
		Title       string `json:"title"`
		Description string `json:"description"`
		Status      string `json:"status"`
		DueAt       string `json:"dueAt"`
		Tag         string `json:"tag"`
	}
	if err := c.ShouldBindJSON(&input); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
		return
	}
	dueAt, err := parseOptionalTime(input.DueAt)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid dueAt"})
		return
	}
	task, err := h.service.CreateTask(userID, projectID, CreateTaskInput{
		Title:       input.Title,
		Description: input.Description,
		Status:      input.Status,
		DueAt:       dueAt,
		Tag:         input.Tag,
	})
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusCreated, task)
}

func (h *Handler) updateTask(c *gin.Context) {
	userID, ok := auth.CurrentUserID(c)
	if !ok {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "missing current user"})
		return
	}
	projectID, ok := parseProjectID(c)
	if !ok {
		return
	}
	taskID, ok := parseTaskID(c)
	if !ok {
		return
	}
	var input struct {
		Title         string `json:"title"`
		Description   string `json:"description"`
		Status        string `json:"status"`
		Completed     bool   `json:"completed"`
		NextProjectID uint   `json:"nextProjectId"`
		Tag           string `json:"tag"`
	}
	if err := c.ShouldBindJSON(&input); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
		return
	}
	task, err := h.service.UpdateTask(userID, projectID, taskID, UpdateTaskInput{
		Title:         input.Title,
		Description:   input.Description,
		Status:        input.Status,
		Completed:     input.Completed,
		NextProjectID: input.NextProjectID,
		Tag:           input.Tag,
	})
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, task)
}

func (h *Handler) uploadSource(c *gin.Context) {
	userID, ok := auth.CurrentUserID(c)
	if !ok {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "missing current user"})
		return
	}
	if h.fileService == nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "file service is not configured"})
		return
	}
	projectID, ok := parseProjectID(c)
	if !ok {
		return
	}
	if err := ensureProjectOwner(userID, projectID); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	fileHeader, err := c.FormFile("file")
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "file is required"})
		return
	}
	file, err := fileHeader.Open()
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "failed to read file"})
		return
	}
	defer file.Close()
	stored, err := h.fileService.Save(projectID, fileHeader.Filename, file)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to store file"})
		return
	}
	title := c.PostForm("title")
	if strings.TrimSpace(title) == "" {
		title = stored.OriginalName
	}
	source, err := h.service.CreateFileSource(userID, projectID, CreateFileSourceInput{
		Title:    title,
		FilePath: stored.RelativePath,
	})
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusCreated, source)
}

func (h *Handler) createCronPlan(c *gin.Context) {
	userID, ok := auth.CurrentUserID(c)
	if !ok {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "missing current user"})
		return
	}
	projectID, ok := parseProjectID(c)
	if !ok {
		return
	}
	var input struct {
		Title     string `json:"title"`
		Schedule  string `json:"schedule"`
		Enabled   bool   `json:"enabled"`
		NextRunAt string `json:"nextRunAt"`
	}
	if err := c.ShouldBindJSON(&input); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
		return
	}
	nextRunAt, err := parseOptionalTime(input.NextRunAt)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid nextRunAt"})
		return
	}
	plan, err := h.service.CreateCronPlan(userID, projectID, CreateCronPlanInput{
		Title:     input.Title,
		Schedule:  input.Schedule,
		Enabled:   input.Enabled,
		NextRunAt: nextRunAt,
	})
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusCreated, plan)
}

func (h *Handler) createTag(c *gin.Context) {
	userID, ok := auth.CurrentUserID(c)
	if !ok {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "missing current user"})
		return
	}
	projectID, ok := parseProjectID(c)
	if !ok {
		return
	}
	var input struct {
		Name string `json:"name"`
	}
	if err := c.ShouldBindJSON(&input); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
		return
	}
	tag, err := h.service.CreateProjectTag(userID, projectID, input.Name)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusCreated, tag)
}

func parseProjectID(c *gin.Context) (uint, bool) {
	projectID, err := strconv.ParseUint(c.Param("id"), 10, 64)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid project id"})
		return 0, false
	}
	return uint(projectID), true
}

func writeProjectError(c *gin.Context, err error) {
	switch {
	case errors.Is(err, gorm.ErrRecordNotFound):
		httpx.Error(c, http.StatusNotFound, "not_found", "项目不存在")
	case errors.Is(err, ErrProjectIdentifierConflict):
		httpx.Error(c, http.StatusConflict, "conflict", "项目标识已存在")
	case errors.Is(err, ErrProjectNameRequired), errors.Is(err, ErrProjectIdentifierRequired):
		httpx.Error(c, http.StatusBadRequest, "invalid_request", "请求参数无效")
	default:
		httpx.Error(c, http.StatusInternalServerError, "internal_error", "项目操作失败")
	}
}

func parseTaskID(c *gin.Context) (uint, bool) {
	taskID, err := strconv.ParseUint(c.Param("taskID"), 10, 64)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid task id"})
		return 0, false
	}
	return uint(taskID), true
}

func parseOptionalTime(value string) (*time.Time, error) {
	trimmed := strings.TrimSpace(value)
	if trimmed == "" {
		return nil, nil
	}
	parsed, err := time.Parse(time.RFC3339, trimmed)
	if err != nil {
		return nil, err
	}
	return &parsed, nil
}
