//
//  Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
//

package handler

import (
	"net/http"
	"strconv"

	"ragflow/internal/common"
	"ragflow/internal/dao"
	"ragflow/internal/entity"
	"ragflow/internal/service"
	dataset "ragflow/internal/service/dataset"
	"ragflow/internal/service/file"

	"github.com/gin-gonic/gin"
	"go.uber.org/zap"
)

// maxArtifactPageSize caps the page_size query parameter for artifact list
// endpoints so a client cannot force an unbounded document-engine search.
const maxArtifactPageSize = 100

// DatasetArtifactHandler exposes the knowledge-compilation artifact REST APIs
// backed by DatasetArtifactService.
type DatasetArtifactHandler struct {
	svc           *service.DatasetArtifactService
	datasetSvc    *dataset.DatasetService
	fileCommitSvc *file.FileCommitService
}

// NewDatasetArtifactHandler creates a DatasetArtifactHandler.
func NewDatasetArtifactHandler(svc *service.DatasetArtifactService, datasetSvc *dataset.DatasetService, fileCommitSvc *file.FileCommitService) *DatasetArtifactHandler {
	return &DatasetArtifactHandler{svc: svc, datasetSvc: datasetSvc, fileCommitSvc: fileCommitSvc}
}

// datasetOwner resolves the dataset and returns its tenant id. It also enforces
// dataset access permission for the requesting user. When the request is
// unauthenticated, the dataset is missing, or the user is not allowed to access
// it, an error response is written and (nil, "", "") is returned so callers can
// abort without sending a second response.
func (h *DatasetArtifactHandler) datasetOwner(c *gin.Context, datasetID string) (*entity.User, string, string) {
	user, code, msg := GetUser(c)
	if code != common.CodeSuccess {
		common.ErrorWithCode(c, code, msg)
		return nil, "", ""
	}
	kb, err := h.datasetSvc.GetKnowledgebaseByID(c.Request.Context(), datasetID)
	if err != nil {
		if dao.IsNotFoundErr(err) {
			common.ErrorWithCode(c, common.CodeNotFound, "dataset not found")
		} else {
			common.ErrorWithCode(c, common.CodeServerError, "failed to resolve dataset")
		}
		return nil, "", ""
	}
	if kb == nil {
		common.ErrorWithCode(c, common.CodeNotFound, "dataset not found")
		return nil, "", ""
	}
	if !h.datasetSvc.Accessible(c.Request.Context(), datasetID, user.ID) {
		common.ErrorWithCode(c, common.CodeForbidden, "no permission to access this dataset")
		return nil, "", ""
	}
	return user, kb.TenantID, msg
}

// HEAD /artifacts — any wiki artifact present?
func (h *DatasetArtifactHandler) AnyArtifact(c *gin.Context) {
	_, tenantID, _ := h.datasetOwner(c, c.Param("dataset_id"))
	if tenantID == "" {
		return
	}
	has, err := h.svc.HasWiki(c.Request.Context(), tenantID, c.Param("dataset_id"))
	if err != nil {
		common.ErrorWithCode(c, common.CodeDataError, err.Error())
		return
	}
	if has {
		c.Status(http.StatusOK)
	} else {
		c.Status(http.StatusNotFound)
	}
}

// GET /artifacts — list wiki pages.
func (h *DatasetArtifactHandler) ListArtifacts(c *gin.Context) {
	_, tenantID, _ := h.datasetOwner(c, c.Param("dataset_id"))
	if tenantID == "" {
		return
	}
	datasetID := c.Param("dataset_id")
	pageType := c.Query("page_type")
	topic := c.Query("topic")
	page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
	pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "30"))
	if page < 1 {
		page = 1
	}
	if pageSize < 1 {
		pageSize = 30
	}
	if pageSize > maxArtifactPageSize {
		pageSize = maxArtifactPageSize
	}
	items, total, err := h.svc.ListWikiPages(c.Request.Context(), tenantID, datasetID, pageType, topic, page, pageSize)
	if err != nil {
		common.ErrorWithCode(c, common.CodeDataError, err.Error())
		return
	}
	common.SuccessWithData(c, gin.H{"total": total, "pages": items}, "success")
}

// PUT /artifacts/<page_type>/<slug> — edit a wiki page.
func (h *DatasetArtifactHandler) UpdateArtifact(c *gin.Context) {
	user, tenantID, _ := h.datasetOwner(c, c.Param("dataset_id"))
	if tenantID == "" {
		return
	}
	datasetID := c.Param("dataset_id")
	pageType := c.Param("page_type")
	slug := c.Param("slug")
	var req struct {
		ContentMd string   `json:"content_md"`
		Title     string   `json:"title"`
		Outlinks  []string `json:"outlinks"`
	}
	if err := c.ShouldBindJSON(&req); err != nil {
		common.ErrorWithCode(c, common.CodeArgumentError, err.Error())
		return
	}

	// Read the current page before mutating so an audit commit can record an
	// accurate old-to-new diff. Only record when the prior state was read
	// successfully; a failed read must not produce a diff against an unknown
	// old value.
	var oldContent string
	canRecordAudit := false
	if req.ContentMd != "" {
		before, gerr := h.svc.GetWikiPage(c.Request.Context(), tenantID, datasetID, pageType, slug)
		if gerr != nil {
			common.Warn("failed to read wiki page before recording edit commit", zap.Error(gerr))
		} else if before != nil {
			oldContent = before.ContentMd
			canRecordAudit = true
		}
	}

	detail, err := h.svc.UpdateWikiPage(c.Request.Context(), tenantID, datasetID, pageType, slug, req.ContentMd, req.Title, req.Outlinks)
	if err != nil {
		common.ErrorWithCode(c, common.CodeDataError, err.Error())
		return
	}
	if detail == nil {
		common.ErrorWithCode(c, common.CodeNotFound, "page not found")
		return
	}

	// Record a wiki-page edit audit commit (git-style parent chain) when the
	// content actually changed. Best-effort: a commit write failure must not
	// roll back the page edit already persisted above.
	if canRecordAudit && req.ContentMd != oldContent && h.fileCommitSvc != nil {
		title := req.Title
		if title == "" {
			title = detail.Title
		}
		if _, cerr := h.fileCommitSvc.RecordPageEdit(c.Request.Context(), file.PageEditCommitInput{
			DatasetID:  datasetID,
			DocID:      pageType + "/" + slug,
			Slug:       slug,
			PageType:   pageType,
			Title:      title,
			AuthorID:   user.ID,
			OldContent: oldContent,
			NewContent: req.ContentMd,
		}); cerr != nil {
			common.Warn("failed to record wiki page edit commit", zap.Error(cerr))
		}
	}

	common.SuccessWithData(c, detail, "success")
}

// GET /artifacts/<page_type>/<slug> — single wiki page.
func (h *DatasetArtifactHandler) GetArtifact(c *gin.Context) {
	_, tenantID, _ := h.datasetOwner(c, c.Param("dataset_id"))
	if tenantID == "" {
		return
	}
	datasetID := c.Param("dataset_id")
	pageType := c.Param("page_type")
	slug := c.Param("slug")
	detail, err := h.svc.GetWikiPage(c.Request.Context(), tenantID, datasetID, pageType, slug)
	if err != nil {
		common.ErrorWithCode(c, common.CodeDataError, err.Error())
		return
	}
	if detail == nil {
		common.ErrorWithCode(c, common.CodeNotFound, "page not found")
		return
	}
	common.SuccessWithData(c, detail, "success")
}

// DELETE /artifacts — clear all wiki artifacts.
func (h *DatasetArtifactHandler) DeleteArtifacts(c *gin.Context) {
	_, tenantID, _ := h.datasetOwner(c, c.Param("dataset_id"))
	if tenantID == "" {
		return
	}
	deleted, err := h.svc.ClearWiki(c.Request.Context(), tenantID, c.Param("dataset_id"))
	if err != nil {
		common.ErrorWithCode(c, common.CodeDataError, err.Error())
		return
	}
	common.SuccessWithData(c, deleted, "success")
}

// GET /artifacts/topics — list wiki topics.
func (h *DatasetArtifactHandler) ListArtifactTopics(c *gin.Context) {
	_, tenantID, _ := h.datasetOwner(c, c.Param("dataset_id"))
	if tenantID == "" {
		return
	}
	datasetID := c.Param("dataset_id")
	items, total, err := h.svc.ListWikiTopics(c.Request.Context(), tenantID, datasetID)
	if err != nil {
		common.ErrorWithCode(c, common.CodeDataError, err.Error())
		return
	}
	common.SuccessWithData(c, gin.H{"total": total, "topics": items}, "success")
}

// GET /artifacts/alteration — wiki alteration summary.
func (h *DatasetArtifactHandler) GetArtifactAlteration(c *gin.Context) {
	_, tenantID, _ := h.datasetOwner(c, c.Param("dataset_id"))
	if tenantID == "" {
		return
	}
	datasetID := c.Param("dataset_id")
	alt, err := h.svc.GetWikiAlteration(c.Request.Context(), tenantID, datasetID)
	if err != nil {
		common.ErrorWithCode(c, common.CodeDataError, err.Error())
		return
	}
	common.SuccessWithData(c, alt, "success")
}

// GET /artifacts/graph — wiki entity/relation graph.
func (h *DatasetArtifactHandler) GetArtifactGraph(c *gin.Context) {
	_, tenantID, _ := h.datasetOwner(c, c.Param("dataset_id"))
	if tenantID == "" {
		return
	}
	datasetID := c.Param("dataset_id")
	graph, err := h.svc.GetWikiGraph(c.Request.Context(), tenantID, datasetID)
	if err != nil {
		common.ErrorWithCode(c, common.CodeDataError, err.Error())
		return
	}
	common.SuccessWithData(c, graph, "success")
}

// GET /artifacts/structure — list compiled structures of a dataset.
func (h *DatasetArtifactHandler) ListStructures(c *gin.Context) {
	_, tenantID, _ := h.datasetOwner(c, c.Param("dataset_id"))
	if tenantID == "" {
		return
	}
	datasetID := c.Param("dataset_id")
	structureKind := c.Query("structure_kind")
	structureIndexType := c.Query("structure_index_type")
	items, total, err := h.svc.ListStructures(c.Request.Context(), tenantID, datasetID, structureKind, structureIndexType)
	if err != nil {
		common.ErrorWithCode(c, common.CodeDataError, err.Error())
		return
	}
	common.SuccessWithData(c, gin.H{"total": total, "structures": items}, "success")
}

// DELETE /artifacts/structure — delete compiled structures of a dataset.
func (h *DatasetArtifactHandler) DeleteStructures(c *gin.Context) {
	_, tenantID, _ := h.datasetOwner(c, c.Param("dataset_id"))
	if tenantID == "" {
		return
	}
	datasetID := c.Param("dataset_id")
	structureKind := c.Query("structure_kind")
	structureIndexType := c.Query("structure_index_type")
	n, err := h.svc.DeleteStructures(c.Request.Context(), tenantID, datasetID, structureKind, structureIndexType)
	if err != nil {
		common.ErrorWithCode(c, common.CodeDataError, err.Error())
		return
	}
	common.SuccessWithData(c, gin.H{"deleted": n}, "success")
}

// HEAD /skills — any skill artifact present?
func (h *DatasetArtifactHandler) AnySkill(c *gin.Context) {
	_, tenantID, _ := h.datasetOwner(c, c.Param("dataset_id"))
	if tenantID == "" {
		return
	}
	has, err := h.svc.HasSkill(c.Request.Context(), tenantID, c.Param("dataset_id"))
	if err != nil {
		common.ErrorWithCode(c, common.CodeDataError, err.Error())
		return
	}
	if has {
		c.Status(http.StatusOK)
	} else {
		c.Status(http.StatusNotFound)
	}
}

// GET /navigation — list navigation clusters.
func (h *DatasetArtifactHandler) ListNavigation(c *gin.Context) {
	_, tenantID, _ := h.datasetOwner(c, c.Param("dataset_id"))
	if tenantID == "" {
		return
	}
	items, total, err := h.svc.ListNavClusters(c.Request.Context(), tenantID, c.Param("dataset_id"))
	if err != nil {
		common.ErrorWithCode(c, common.CodeDataError, err.Error())
		return
	}
	common.SuccessWithData(c, gin.H{"total": total, "nav": items}, "success")
}

// DELETE /navigation — delete all navigation clusters.
func (h *DatasetArtifactHandler) DeleteNavigation(c *gin.Context) {
	_, tenantID, _ := h.datasetOwner(c, c.Param("dataset_id"))
	if tenantID == "" {
		return
	}
	n, err := h.svc.DeleteNav(c.Request.Context(), tenantID, c.Param("dataset_id"))
	if err != nil {
		common.ErrorWithCode(c, common.CodeDataError, err.Error())
		return
	}
	common.SuccessWithData(c, gin.H{"deleted": n}, "success")
}

// DELETE /navigation/<name> — delete a single navigation cluster.
func (h *DatasetArtifactHandler) DeleteNavigationNode(c *gin.Context) {
	_, tenantID, _ := h.datasetOwner(c, c.Param("dataset_id"))
	if tenantID == "" {
		return
	}
	n, err := h.svc.DeleteNavNode(c.Request.Context(), tenantID, c.Param("dataset_id"), c.Param("name"))
	if err != nil {
		common.ErrorWithCode(c, common.CodeDataError, err.Error())
		return
	}
	common.SuccessWithData(c, gin.H{"deleted": n}, "success")
}

// GET /navigation/<name>/children — list children of a navigation cluster.
func (h *DatasetArtifactHandler) ListNavigationChildren(c *gin.Context) {
	_, tenantID, _ := h.datasetOwner(c, c.Param("dataset_id"))
	if tenantID == "" {
		return
	}
	items, total, err := h.svc.ListNavChildren(c.Request.Context(), tenantID, c.Param("dataset_id"), c.Param("name"))
	if err != nil {
		common.ErrorWithCode(c, common.CodeDataError, err.Error())
		return
	}
	common.SuccessWithData(c, gin.H{"total": total, "children": items}, "success")
}

// GET /skills — skill tree.
func (h *DatasetArtifactHandler) GetSkillTree(c *gin.Context) {
	_, tenantID, _ := h.datasetOwner(c, c.Param("dataset_id"))
	if tenantID == "" {
		return
	}
	kwd := c.Query("kwd")
	items, total, err := h.svc.GetSkillTree(c.Request.Context(), tenantID, c.Param("dataset_id"), kwd)
	if err != nil {
		common.ErrorWithCode(c, common.CodeDataError, err.Error())
		return
	}
	common.SuccessWithData(c, gin.H{"total": total, "tree": items}, "success")
}

// DELETE /skills — delete all skills.
func (h *DatasetArtifactHandler) DeleteSkills(c *gin.Context) {
	_, tenantID, _ := h.datasetOwner(c, c.Param("dataset_id"))
	if tenantID == "" {
		return
	}
	n, err := h.svc.DeleteSkills(c.Request.Context(), tenantID, c.Param("dataset_id"), "")
	if err != nil {
		common.ErrorWithCode(c, common.CodeDataError, err.Error())
		return
	}
	common.SuccessWithData(c, gin.H{"deleted": n}, "success")
}

// GET /skills/<skill_kwd> — single skill page.
func (h *DatasetArtifactHandler) GetSkillPage(c *gin.Context) {
	_, tenantID, _ := h.datasetOwner(c, c.Param("dataset_id"))
	if tenantID == "" {
		return
	}
	detail, err := h.svc.GetSkillPage(c.Request.Context(), tenantID, c.Param("dataset_id"), c.Param("skill_kwd"))
	if err != nil {
		common.ErrorWithCode(c, common.CodeDataError, err.Error())
		return
	}
	if detail == nil {
		common.ErrorWithCode(c, common.CodeNotFound, "skill not found")
		return
	}
	common.SuccessWithData(c, detail, "success")
}

// DELETE /skills/<skill_kwd> — delete a single skill.
func (h *DatasetArtifactHandler) DeleteSkill(c *gin.Context) {
	_, tenantID, _ := h.datasetOwner(c, c.Param("dataset_id"))
	if tenantID == "" {
		return
	}
	n, err := h.svc.DeleteSkills(c.Request.Context(), tenantID, c.Param("dataset_id"), c.Param("skill_kwd"))
	if err != nil {
		common.ErrorWithCode(c, common.CodeDataError, err.Error())
		return
	}
	common.SuccessWithData(c, gin.H{"deleted": n}, "success")
}

// GET /documents/<document_id>/structure/graph — document structure graph.
func (h *DatasetArtifactHandler) GetDocumentGraph(c *gin.Context) {
	_, tenantID, _ := h.datasetOwner(c, c.Param("dataset_id"))
	if tenantID == "" {
		return
	}
	datasetID := c.Param("dataset_id")
	documentID := c.Param("document_id")
	graphType := c.Query("graph_type")
	items, total, err := h.svc.GetDocumentGraph(c.Request.Context(), tenantID, datasetID, documentID, graphType)
	if err != nil {
		common.ErrorWithCode(c, common.CodeDataError, err.Error())
		return
	}
	common.SuccessWithData(c, gin.H{"total": total, "graph": items}, "success")
}

// DELETE /documents/<document_id>/structure/graph — delete document structure graph.
func (h *DatasetArtifactHandler) DeleteDocumentGraph(c *gin.Context) {
	_, tenantID, _ := h.datasetOwner(c, c.Param("dataset_id"))
	if tenantID == "" {
		return
	}
	datasetID := c.Param("dataset_id")
	documentID := c.Param("document_id")
	n, err := h.svc.DeleteDocumentGraph(c.Request.Context(), tenantID, datasetID, documentID)
	if err != nil {
		common.ErrorWithCode(c, common.CodeDataError, err.Error())
		return
	}
	common.SuccessWithData(c, gin.H{"deleted": n}, "success")
}
