package dataset

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/models"
)

type Handler struct {
	service *Service
}

type SourceDTO struct {
	ID           string     `json:"id"`
	Name         string     `json:"name"`
	Kind         string     `json:"kind"`
	URL          string     `json:"url"`
	IconURL      string     `json:"iconUrl"`
	Description  string     `json:"description"`
	Enabled      bool       `json:"enabled"`
	BuiltIn      bool       `json:"builtIn"`
	LastSyncedAt *time.Time `json:"lastSyncedAt"`
	ItemCount    int64      `json:"itemCount"`
	CreatedAt    time.Time  `json:"createdAt"`
	UpdatedAt    time.Time  `json:"updatedAt"`
}

type ItemDTO struct {
	ID          string     `json:"id"`
	SourceID    string     `json:"sourceId"`
	Title       string     `json:"title"`
	Summary     string     `json:"summary"`
	Content     string     `json:"content"`
	URL         string     `json:"url"`
	Status      string     `json:"status"`
	Starred     bool       `json:"starred"`
	PublishedAt *time.Time `json:"publishedAt"`
	CreatedAt   time.Time  `json:"createdAt"`
	UpdatedAt   time.Time  `json:"updatedAt"`
}

type ItemPageDTO struct {
	Items  []ItemDTO `json:"items"`
	Total  int64     `json:"total"`
	Limit  int       `json:"limit"`
	Offset int       `json:"offset"`
}

type CronDTO struct {
	ID         string     `json:"id"`
	SourceID   string     `json:"sourceId"`
	Status     string     `json:"status"`
	LastRunAt  *time.Time `json:"lastRunAt"`
	LastResult string     `json:"lastResult"`
	CreatedAt  time.Time  `json:"createdAt"`
	UpdatedAt  time.Time  `json:"updatedAt"`
}

type DepositDTO struct {
	NoteID    string `json:"noteId"`
	ProjectID string `json:"projectId"`
}

type sourceRequest struct {
	Name        string `json:"name"`
	Kind        string `json:"kind"`
	URL         string `json:"url"`
	IconURL     string `json:"iconUrl"`
	Description string `json:"description"`
	Enabled     *bool  `json:"enabled"`
}

func NewHandler(service *Service) *Handler {
	return &Handler{service: service}
}

func (h *Handler) Register(router gin.IRouter) {
	router.GET("/dataset-sources", h.listSources)
	router.POST("/dataset-sources", h.createSource)
	router.PATCH("/dataset-sources/:id", h.updateSource)
	router.DELETE("/dataset-sources/:id", h.deleteSource)
	router.GET("/dataset-items", h.listItems)
	router.POST("/dataset-items", h.createItem)
	router.PATCH("/dataset-items/:id", h.updateItem)
	router.POST("/dataset-items/:id/deposit", h.depositItem)
	router.GET("/dataset-crons", h.listCrons)
	router.POST("/dataset-crons/sync", h.queueSync)
}

func (h *Handler) listSources(c *gin.Context) {
	userID, ok := currentUser(c)
	if !ok {
		return
	}
	records, err := h.service.ListSources(userID)
	if err != nil {
		writeError(c, err)
		return
	}
	items := make([]SourceDTO, 0, len(records))
	for _, record := range records {
		items = append(items, sourceDTO(record.Source, record.ItemCount))
	}
	c.JSON(http.StatusOK, items)
}

func (h *Handler) createSource(c *gin.Context) {
	userID, ok := currentUser(c)
	if !ok {
		return
	}
	var input sourceRequest
	if err := c.ShouldBindJSON(&input); err != nil {
		writeInvalidRequest(c)
		return
	}
	enabled := true
	if input.Enabled != nil {
		enabled = *input.Enabled
	}
	source, err := h.service.CreateSource(userID, SourceInput{
		Name: input.Name, Kind: input.Kind, URL: input.URL, IconURL: input.IconURL,
		Description: input.Description, Enabled: enabled,
	})
	if err != nil {
		writeError(c, err)
		return
	}
	c.JSON(http.StatusCreated, sourceDTO(*source, 0))
}

func (h *Handler) updateSource(c *gin.Context) {
	userID, ok := currentUser(c)
	if !ok {
		return
	}
	identity, ok := httpx.IdentityParam(c, "id")
	if !ok {
		return
	}
	var input struct {
		Name        *string `json:"name"`
		Kind        *string `json:"kind"`
		URL         *string `json:"url"`
		IconURL     *string `json:"iconUrl"`
		Description *string `json:"description"`
		Enabled     *bool   `json:"enabled"`
	}
	if err := c.ShouldBindJSON(&input); err != nil {
		writeInvalidRequest(c)
		return
	}
	source, err := h.service.PatchSource(userID, identity, SourcePatch{
		Name: input.Name, Kind: input.Kind, URL: input.URL, IconURL: input.IconURL,
		Description: input.Description, Enabled: input.Enabled,
	})
	if err != nil {
		writeError(c, err)
		return
	}
	itemCount, err := h.service.CountItems(userID, source.ID)
	if err != nil {
		writeError(c, err)
		return
	}
	c.JSON(http.StatusOK, sourceDTO(*source, itemCount))
}

func (h *Handler) deleteSource(c *gin.Context) {
	userID, ok := currentUser(c)
	if !ok {
		return
	}
	identity, ok := httpx.IdentityParam(c, "id")
	if !ok {
		return
	}
	if err := h.service.DeleteSource(userID, identity); err != nil {
		writeError(c, err)
		return
	}
	c.Status(http.StatusNoContent)
}

func (h *Handler) listItems(c *gin.Context) {
	userID, ok := currentUser(c)
	if !ok {
		return
	}
	limit, err := optionalNonNegativeInt(c.Query("limit"))
	if err != nil {
		writeInvalidRequest(c)
		return
	}
	offset, err := optionalNonNegativeInt(c.Query("offset"))
	if err != nil {
		writeInvalidRequest(c)
		return
	}
	page, err := h.service.ListItemsPage(userID, c.Query("sourceId"), limit, offset)
	if err != nil {
		writeError(c, err)
		return
	}
	result := make([]ItemDTO, 0, len(page.Items))
	for _, item := range page.Items {
		result = append(result, itemDTO(item))
	}
	_, hasLimit := c.GetQuery("limit")
	_, hasOffset := c.GetQuery("offset")
	if !hasLimit && !hasOffset {
		c.JSON(http.StatusOK, result)
		return
	}
	c.JSON(http.StatusOK, ItemPageDTO{
		Items: result, Total: page.Total, Limit: page.Limit, Offset: page.Offset,
	})
}

func (h *Handler) createItem(c *gin.Context) {
	userID, ok := currentUser(c)
	if !ok {
		return
	}
	var input struct {
		SourceID    string `json:"sourceId"`
		Title       string `json:"title"`
		Summary     string `json:"summary"`
		Content     string `json:"content"`
		URL         string `json:"url"`
		PublishedAt string `json:"publishedAt"`
	}
	if err := c.ShouldBindJSON(&input); err != nil {
		writeInvalidRequest(c)
		return
	}
	publishedAt, err := parseOptionalTime(input.PublishedAt)
	if err != nil {
		writeInvalidRequest(c)
		return
	}
	item, err := h.service.CreateItem(userID, ItemInput{
		SourceIdentity: input.SourceID, Title: input.Title, Summary: input.Summary,
		Content: input.Content, URL: input.URL, PublishedAt: publishedAt,
	})
	if err != nil {
		writeError(c, err)
		return
	}
	c.JSON(http.StatusCreated, itemDTO(*item))
}

func (h *Handler) updateItem(c *gin.Context) {
	userID, ok := currentUser(c)
	if !ok {
		return
	}
	identity, ok := httpx.IdentityParam(c, "id")
	if !ok {
		return
	}
	var input struct {
		Status  *string `json:"status"`
		Starred *bool   `json:"starred"`
	}
	if err := c.ShouldBindJSON(&input); err != nil {
		writeInvalidRequest(c)
		return
	}
	item, err := h.service.PatchItem(userID, identity, ItemPatch{Status: input.Status, Starred: input.Starred})
	if err != nil {
		writeError(c, err)
		return
	}
	c.JSON(http.StatusOK, itemDTO(*item))
}

func (h *Handler) depositItem(c *gin.Context) {
	userID, ok := currentUser(c)
	if !ok {
		return
	}
	identity, ok := httpx.IdentityParam(c, "id")
	if !ok {
		return
	}
	var input struct {
		ProjectID string `json:"projectId"`
	}
	if err := c.ShouldBindJSON(&input); err != nil || strings.TrimSpace(input.ProjectID) == "" {
		writeInvalidRequest(c)
		return
	}
	result, err := h.service.DepositItem(userID, identity, input.ProjectID)
	if err != nil {
		writeError(c, err)
		return
	}
	c.JSON(http.StatusCreated, DepositDTO{
		NoteID: result.NoteIdentity, ProjectID: result.ProjectIdentity,
	})
}

func (h *Handler) listCrons(c *gin.Context) {
	userID, ok := currentUser(c)
	if !ok {
		return
	}
	crons, err := h.service.ListCrons(userID)
	if err != nil {
		writeError(c, err)
		return
	}
	result := make([]CronDTO, 0, len(crons))
	for _, cron := range crons {
		result = append(result, cronDTO(cron))
	}
	c.JSON(http.StatusOK, result)
}

func (h *Handler) queueSync(c *gin.Context) {
	userID, ok := currentUser(c)
	if !ok {
		return
	}
	crons, err := h.service.QueueSources(userID)
	if err != nil {
		writeError(c, err)
		return
	}
	result := make([]CronDTO, 0, len(crons))
	for _, cron := range crons {
		result = append(result, cronDTO(cron))
	}
	c.JSON(http.StatusAccepted, result)
}

func currentUser(c *gin.Context) (uint, bool) {
	userID, ok := auth.CurrentUserID(c)
	if !ok {
		httpx.Error(c, http.StatusUnauthorized, "unauthorized", "未登录或登录已失效")
		return 0, false
	}
	return userID, true
}

func writeError(c *gin.Context, err error) {
	switch {
	case errors.Is(err, gorm.ErrRecordNotFound):
		httpx.Error(c, http.StatusNotFound, "not_found", "数据源或数据条目不存在")
	case errors.Is(err, ErrBuiltInSource):
		httpx.Error(c, http.StatusConflict, "built_in_source", "内置数据源不能删除，可以将其停用")
	case errors.Is(err, ErrSyncInProgress):
		httpx.Error(c, http.StatusConflict, "sync_in_progress", "已有数据源采集任务正在运行")
	case errors.Is(err, ErrNameRequired), errors.Is(err, ErrKindInvalid),
		errors.Is(err, ErrURLInvalid), errors.Is(err, ErrTitleRequired), errors.Is(err, ErrStatusInvalid):
		writeInvalidRequest(c)
	default:
		httpx.Error(c, http.StatusInternalServerError, "internal_error", "探索数据操作失败")
	}
}

func writeInvalidRequest(c *gin.Context) {
	httpx.Error(c, http.StatusBadRequest, "invalid_request", "请求参数无效")
}

func sourceDTO(source models.SaDatasetSource, itemCount int64) SourceDTO {
	return SourceDTO{
		ID: source.Identity, Name: source.Name, Kind: source.Kind, URL: source.URL,
		IconURL: source.IconURL, Description: source.Description, Enabled: source.Enabled,
		BuiltIn: source.SeedKey != nil, LastSyncedAt: utcTime(source.LastSyncedAt), ItemCount: itemCount,
		CreatedAt: source.CreatedAt.UTC(), UpdatedAt: source.UpdatedAt.UTC(),
	}
}

func itemDTO(item models.SaDatasetItem) ItemDTO {
	return ItemDTO{
		ID: item.Identity, SourceID: item.SourceIdentity, Title: item.Title,
		Summary: item.Summary, Content: item.Content, URL: item.URL, Status: item.Status,
		Starred: item.Starred, PublishedAt: utcTime(item.PublishedAt),
		CreatedAt: item.CreatedAt.UTC(), UpdatedAt: item.UpdatedAt.UTC(),
	}
}

func cronDTO(cron models.SaDatasetCron) CronDTO {
	return CronDTO{
		ID: cron.Identity, SourceID: cron.SourceIdentity, Status: cron.Status,
		LastRunAt: utcTime(cron.LastRunAt), LastResult: cron.LastResult,
		CreatedAt: cron.CreatedAt.UTC(), UpdatedAt: cron.UpdatedAt.UTC(),
	}
}

func optionalNonNegativeInt(value string) (int, error) {
	if strings.TrimSpace(value) == "" {
		return 0, nil
	}
	parsed, err := strconv.Atoi(value)
	if err != nil || parsed < 0 {
		return 0, errors.New("invalid non-negative integer")
	}
	return parsed, nil
}

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

func utcTime(value *time.Time) *time.Time {
	if value == nil {
		return nil
	}
	result := value.UTC()
	return &result
}
