package tech.easyflow.admin.controller.ai;

import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.io.IoUtil;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.MediaType;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import tech.easyflow.ai.documentimport.DocumentImportDtos;
import tech.easyflow.ai.documentimport.task.DocumentImportTaskStatusStreamService;
import tech.easyflow.ai.entity.Document;
import tech.easyflow.ai.entity.DocumentCollection;
import tech.easyflow.ai.entity.DocumentCollectionSplitParams;
import tech.easyflow.ai.service.DocumentChunkService;
import tech.easyflow.ai.service.DocumentCollectionService;
import tech.easyflow.ai.service.DocumentService;
import tech.easyflow.ai.service.ModelService;
import tech.easyflow.common.annotation.UsePermission;
import tech.easyflow.common.cache.RedisLockExecutor;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.tree.Tree;
import tech.easyflow.common.util.RequestUtil;
import tech.easyflow.common.util.StringUtil;
import tech.easyflow.common.web.controller.BaseCurdController;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.common.web.jsonbody.JsonBody;
import tech.easyflow.common.filestorage.FileStorageService;
import tech.easyflow.system.enums.CategoryResourceType;
import tech.easyflow.system.enums.ResourceAction;
import tech.easyflow.system.enums.ResourceLookup;
import tech.easyflow.system.permission.resource.RequireResourceAccess;
import tech.easyflow.system.service.ResourceAccessService;

import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.math.BigInteger;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * 控制层。
 *
 * @author michael
 * @since 2024-08-23
 */
@RestController
@RequestMapping("/api/v1/document")
@UsePermission(moduleName = "/api/v1/documentCollection")
public class DocumentController extends BaseCurdController<DocumentService, Document> {

    private static final String DOCUMENT_ORDER_LOCK_KEY_PREFIX = "easyflow:lock:document:order:";
    private static final Duration LOCK_WAIT_TIMEOUT = Duration.ofSeconds(2);
    private static final Duration LOCK_LEASE_TIMEOUT = Duration.ofSeconds(10);

    private final DocumentCollectionService knowledgeService;


    @Autowired
    private DocumentService documentService;

    @Autowired
    private RedisLockExecutor redisLockExecutor;

    @Resource(name = "default")
    private FileStorageService storageService;

    @Autowired
    private ResourceAccessService resourceAccessService;

    @Autowired
    private DocumentImportTaskStatusStreamService documentImportTaskStatusStreamService;

    @Value("${easyflow.storage.local.root:}")
    private  String fileUploadPath;


    public DocumentController(DocumentService service,
                              DocumentCollectionService knowledgeService,
                              DocumentChunkService documentChunkService, ModelService modelService) {
        super(service);
        this.knowledgeService = knowledgeService;
    }
    @PostMapping("removeDoc")
    @Transactional
    @SaCheckPermission("/api/v1/documentCollection/remove")
    public Result<?> remove(@JsonBody(value = "id", required = true) String id) {
        Document document = requireDocument(new BigInteger(id));
        getDocumentCollection(document.getCollectionId().toString(), ResourceAction.MANAGE, "无权限管理知识库");
        List<Serializable> ids = Collections.singletonList(id);
        Result<?> result = onRemoveBefore(ids);
        if (result != null) return result;
        boolean isSuccess = documentService.removeDoc(id);
        if (!isSuccess){
            return Result.ok(false);
        }
        boolean success = service.removeById(id);
        onRemoveAfter(ids);
        return Result.ok(success);
    }


    /**
     * 查询所有所有数据
     *
     * @param entity
     * @param asTree
     * @param sortKey
     * @param sortType
     * @return 所有数据
     */
    @GetMapping("list")
    @Override
    @SaCheckPermission("/api/v1/documentCollection/query")
    public Result<List<Document>> list(Document entity, Boolean asTree, String sortKey, String sortType) {
        String kbSlug = RequestUtil.getParamAsString("id");
        if (StringUtil.noText(kbSlug)) {
            throw new BusinessException("知识库id不能为空");
        }

        DocumentCollection knowledge = getDocumentCollection(kbSlug, ResourceAction.READ, "无权限访问知识库");

        QueryWrapper queryWrapper = QueryWrapper.create()
                .eq(Document::getCollectionId, knowledge.getId());
        queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy()));
        List<Document> documents = service.list(queryWrapper);
        List<Document> list = Tree.tryToTree(documents, asTree);
        return Result.ok(list);
    }

    @GetMapping("documentList")
    @SaCheckPermission("/api/v1/documentCollection/query")
    public Result<Page<Document>> documentList(@RequestParam(name="title", required = false) String fileName, @RequestParam(name="pageSize") int pageSize, @RequestParam(name = "pageNumber") int pageNumber) {
        String kbSlug = RequestUtil.getParamAsString("id");
        if (StringUtil.noText(kbSlug)) {
            throw new BusinessException("知识库id不能为空");
        }
        DocumentCollection knowledge = getDocumentCollection(kbSlug, ResourceAction.READ, "无权限访问知识库");
        Page<Document> documentList = documentService.getDocumentList(knowledge.getId().toString(), pageSize, pageNumber,fileName);
        return Result.ok(documentList);
    }

    @GetMapping("download")
    @SaCheckPermission("/api/v1/documentCollection/query")
    @RequireResourceAccess(
            resource = CategoryResourceType.KNOWLEDGE,
            action = ResourceAction.READ,
            lookup = ResourceLookup.DOCUMENT_ID,
            idExpr = "#documentId",
            denyMessage = "无权限访问知识库"
    )
    public void download(@RequestParam BigInteger documentId, HttpServletResponse response) throws IOException {
        Document document = requireDocument(documentId);
        String fileName = resolveDownloadFileName(document);
        response.setContentType("application/octet-stream");
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8).replaceAll("\\+", "%20");
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + encodedFileName);
        try (InputStream inputStream = storageService.readStream(document.getDocumentPath())) {
            IoUtil.copy(inputStream, response.getOutputStream());
            response.flushBuffer();
        }
    }


    @Override
    protected String getDefaultOrderBy() {
        return "order_no asc";
    }


    @PostMapping("update")
    @Override
    @Transactional
    @SaCheckPermission("/api/v1/documentCollection/save")
    public Result<Boolean> update(@JsonBody Document entity) {
        if (entity.getId() == null) {
            throw new BusinessException("文档不存在");
        }
        Document current = requireDocument(entity.getId());
        getDocumentCollection(current.getCollectionId().toString(), ResourceAction.MANAGE, "无权限管理知识库");
        super.update(entity);
        return Result.ok(updatePosition(entity));
    }

    /**
     * 文本拆分/保存
     *
     */
    @PostMapping(value = {"textSplit", "/saveText"})
    @SaCheckPermission("/api/v1/documentCollection/save")
    public Result<?> textSplit(@JsonBody DocumentCollectionSplitParams documentCollectionSplitParams) {
        if (documentCollectionSplitParams.getKnowledgeId() == null) {
            throw new BusinessException("知识库id不能为空");
        }
        getDocumentCollection(documentCollectionSplitParams.getKnowledgeId().toString(), ResourceAction.MANAGE, "无权限管理知识库");
        return documentService.textSplit(documentCollectionSplitParams);
    }

    @PostMapping("import/analyze")
    @SaCheckPermission("/api/v1/documentCollection/save")
    public Result<DocumentImportDtos.AnalyzeResponse> analyzeImport(@JsonBody DocumentImportDtos.AnalyzeRequest request) {
        if (request.getKnowledgeId() == null) {
            throw new BusinessException("知识库id不能为空");
        }
        getDocumentCollection(request.getKnowledgeId().toString(), ResourceAction.MANAGE, "无权限管理知识库");
        return documentService.analyzeImport(request);
    }

    @PostMapping("import/preview")
    @SaCheckPermission("/api/v1/documentCollection/save")
    public Result<DocumentImportDtos.PreviewResponse> previewImport(@JsonBody DocumentImportDtos.PreviewRequest request) {
        if (request.getKnowledgeId() == null) {
            throw new BusinessException("知识库id不能为空");
        }
        getDocumentCollection(request.getKnowledgeId().toString(), ResourceAction.MANAGE, "无权限管理知识库");
        return documentService.previewImport(request);
    }

    @PostMapping("import/commit")
    @SaCheckPermission("/api/v1/documentCollection/save")
    public Result<DocumentImportDtos.CommitResponse> commitImport(@JsonBody DocumentImportDtos.CommitRequest request) {
        if (request.getKnowledgeId() == null) {
            throw new BusinessException("知识库id不能为空");
        }
        getDocumentCollection(request.getKnowledgeId().toString(), ResourceAction.MANAGE, "无权限管理知识库");
        return documentService.commitImport(request);
    }

    @PostMapping("import/task/create")
    @SaCheckPermission("/api/v1/documentCollection/save")
    public Result<DocumentImportDtos.TaskCreateResponse> createImportTask(@JsonBody DocumentImportDtos.TaskCreateRequest request) {
        if (request.getKnowledgeId() == null) {
            throw new BusinessException("知识库id不能为空");
        }
        getDocumentCollection(request.getKnowledgeId().toString(), ResourceAction.MANAGE, "无权限管理知识库");
        return documentService.createImportTask(request);
    }

    @GetMapping("import/task/detail")
    @SaCheckPermission("/api/v1/documentCollection/query")
    public Result<DocumentImportDtos.TaskDetailResponse> getImportTaskDetail(@RequestParam BigInteger taskId) {
        Result<DocumentImportDtos.TaskDetailResponse> result = documentService.getImportTaskDetail(taskId);
        if (result.getData() != null && result.getData().getKnowledgeId() != null) {
            getDocumentCollection(result.getData().getKnowledgeId().toString(), ResourceAction.READ, "无权限访问知识库");
        }
        return result;
    }

    /**
     * 订阅知识库文档任务状态流。
     *
     * @param knowledgeId 知识库 ID
     * @return SSE 推送连接
     */
    @PostMapping(value = "import/task/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    @SaCheckPermission("/api/v1/documentCollection/query")
    public SseEmitter streamImportTask(@JsonBody(value = "knowledgeId", required = true) BigInteger knowledgeId) {
        getDocumentCollection(knowledgeId.toString(), ResourceAction.READ, "无权限访问知识库");
        return documentImportTaskStatusStreamService.subscribe(knowledgeId);
    }

    @PostMapping("import/task/preview")
    @SaCheckPermission("/api/v1/documentCollection/save")
    public Result<DocumentImportDtos.PreviewResponse> previewImportTask(@JsonBody DocumentImportDtos.PreviewRequest request) {
        if (request.getKnowledgeId() == null) {
            throw new BusinessException("知识库id不能为空");
        }
        getDocumentCollection(request.getKnowledgeId().toString(), ResourceAction.MANAGE, "无权限管理知识库");
        return documentService.previewImportTask(request);
    }

    @PostMapping("import/task/startIndex")
    @SaCheckPermission("/api/v1/documentCollection/save")
    public Result<DocumentImportDtos.TaskStartIndexResponse> startIndexTask(@JsonBody DocumentImportDtos.TaskStartIndexRequest request) {
        if (request.getKnowledgeId() == null) {
            throw new BusinessException("知识库id不能为空");
        }
        getDocumentCollection(request.getKnowledgeId().toString(), ResourceAction.MANAGE, "无权限管理知识库");
        return documentService.startIndexTask(request);
    }

    @PostMapping("import/task/retryParse")
    @SaCheckPermission("/api/v1/documentCollection/save")
    public Result<DocumentImportDtos.TaskStartIndexResponse> retryParseTask(@JsonBody DocumentImportDtos.TaskRetryRequest request) {
        if (request.getKnowledgeId() == null) {
            throw new BusinessException("知识库id不能为空");
        }
        getDocumentCollection(request.getKnowledgeId().toString(), ResourceAction.MANAGE, "无权限管理知识库");
        return documentService.retryParseTask(request);
    }

    @PostMapping("import/task/retryIndex")
    @SaCheckPermission("/api/v1/documentCollection/save")
    public Result<DocumentImportDtos.TaskStartIndexResponse> retryIndexTask(@JsonBody DocumentImportDtos.TaskRetryRequest request) {
        if (request.getKnowledgeId() == null) {
            throw new BusinessException("知识库id不能为空");
        }
        getDocumentCollection(request.getKnowledgeId().toString(), ResourceAction.MANAGE, "无权限管理知识库");
        return documentService.retryIndexTask(request);
    }

    /**
     * 更新 entity
     *
     * @param entity
     * @return Result
     */
    private boolean updatePosition(Document entity) {
        Integer orderNo = entity.getOrderNo();
        if (orderNo != null) {
            if (orderNo <= 0) orderNo = 0;
            Document current = service.getById(entity.getId());
            if (current == null) {
                throw new BusinessException("文档不存在");
            }
            BigInteger knowledgeId = current.getCollectionId();
            Integer targetOrderNo = orderNo;

            redisLockExecutor.executeWithLock(
                    DOCUMENT_ORDER_LOCK_KEY_PREFIX + knowledgeId,
                    LOCK_WAIT_TIMEOUT,
                    LOCK_LEASE_TIMEOUT,
                    () -> {
                        List<Document> list = service.list(QueryWrapper.create()
                                .eq(Document::getCollectionId, knowledgeId)
                                .orderBy(getDefaultOrderBy())
                        );

                        list.removeIf(item -> item.getId().equals(entity.getId()));
                        if (targetOrderNo >= list.size()) {
                            list.add(entity);
                        } else {
                            list.add(targetOrderNo, entity);
                        }

                        List<Document> updateList = new ArrayList<>();
                        for (int i = 0; i < list.size(); i++) {
                            Document updateItem = new Document();
                            updateItem.setId(list.get(i).getId());
                            updateItem.setOrderNo(i);
                            updateList.add(updateItem);
                        }

                        service.updateBatch(updateList);
                    }
            );
        }

        return true;
    }


    public String getRootPath() {
        if (StringUtil.hasText(this.fileUploadPath)) {
            return this.fileUploadPath;
        }
        ClassPathResource fileResource = new ClassPathResource("/");
        try {
            return new File(fileResource.getFile(), "/public").getAbsolutePath();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private DocumentCollection getDocumentCollection(String idOrSlug, ResourceAction action, String denyMessage) {
        DocumentCollection knowledge = StringUtil.isNumeric(idOrSlug)
                ? knowledgeService.getById(idOrSlug)
                : knowledgeService.getOne(QueryWrapper.create().eq(DocumentCollection::getSlug, idOrSlug));
        if (knowledge == null) {
            throw new BusinessException("知识库不存在");
        }
        resourceAccessService.assertAccess(CategoryResourceType.KNOWLEDGE, knowledge, action, denyMessage);
        if (knowledge.isFaqCollection()) {
            throw new BusinessException("FAQ知识库不支持文档操作");
        }
        return knowledge;
    }

    private Document requireDocument(BigInteger documentId) {
        if (documentId == null) {
            throw new BusinessException("文档不存在");
        }
        Document document = service.getById(documentId);
        if (document == null) {
            throw new BusinessException("文档不存在");
        }
        return document;
    }

    private String resolveDownloadFileName(Document document) {
        String fileName = document.getTitle();
        if (!StringUtil.hasText(fileName)) {
            String path = document.getDocumentPath();
            if (!StringUtil.hasText(path)) {
                return "document";
            }
            int slashIndex = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\'));
            return slashIndex >= 0 ? path.substring(slashIndex + 1) : path;
        }
        return fileName;
    }

}
