package tech.easyflow.publicapi.controller;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.Part;
import org.springframework.http.InvalidMediaTypeException;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import tech.easyflow.ai.documentimport.ImportCallerContext;
import tech.easyflow.ai.documentimport.ImportCallerType;
import tech.easyflow.ai.documentimport.PublicDocumentImportDtos;
import tech.easyflow.ai.documentimport.task.KnowledgeImportBatchFacade;
import tech.easyflow.ai.entity.DocumentCollection;
import tech.easyflow.ai.enums.KnowledgeApiPermissionScope;
import tech.easyflow.ai.service.DocumentCollectionService;
import tech.easyflow.ai.service.KnowledgeShareAuditService;
import tech.easyflow.ai.service.KnowledgeSharePermissionService;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.common.web.jsonbody.JsonBody;
import tech.easyflow.publicapi.interceptor.PublicApiInterceptor;
import tech.easyflow.system.entity.SysApiKey;
import tech.easyflow.system.service.SysApiKeyService;

import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 知识库文档 Public API 批量异步导入接口。
 *
 * @author Codex
 * @since 2026-08-02
 */
@RestController
@RequestMapping(
    value = "/public-api/knowledge-share/document/import/batch",
    produces = MediaType.APPLICATION_JSON_VALUE
)
public class PublicKnowledgeDocumentImportController {

    private static final long MAX_METADATA_BYTES = 1024L * 1024L;

    private final SysApiKeyService sysApiKeyService;
    private final KnowledgeSharePermissionService permissionService;
    private final KnowledgeShareAuditService auditService;
    private final DocumentCollectionService documentCollectionService;
    private final KnowledgeImportBatchFacade importFacade;

    /**
     * 创建 Public API 批量导入控制器。
     *
     * @param sysApiKeyService 访问令牌服务
     * @param permissionService 知识库权限服务
     * @param auditService 审计服务
     * @param documentCollectionService 知识库服务
     * @param importFacade 批量导入门面
     */
    public PublicKnowledgeDocumentImportController(
        SysApiKeyService sysApiKeyService,
        KnowledgeSharePermissionService permissionService,
        KnowledgeShareAuditService auditService,
        DocumentCollectionService documentCollectionService,
        KnowledgeImportBatchFacade importFacade) {
        this.sysApiKeyService = sysApiKeyService;
        this.permissionService = permissionService;
        this.auditService = auditService;
        this.documentCollectionService = documentCollectionService;
        this.importFacade = importFacade;
    }

    /**
     * 接收多文件并创建异步导入任务。
     *
     * @param apiKey 访问令牌
     * @param metadataPart JSON 元数据 Part
     * @param files 多个文件 Part
     * @param servletRequest Servlet 请求
     * @return HTTP 202 异步任务响应
     */
    @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public ResponseEntity<Result<PublicDocumentImportDtos.SubmitResponse>> submit(
        @RequestHeader("ApiKey") String apiKey,
        @RequestPart("metadata") Part metadataPart,
        @RequestPart("files") List<MultipartFile> files,
        HttpServletRequest servletRequest) {
        PublicDocumentImportDtos.BatchMetadata metadata =
            parseMetadata(metadataPart);
        SysApiKey token = resolveAuthenticatedApiKey(servletRequest, apiKey);
        assertImportPermission(
            token,
            servletRequest.getRequestURI(),
            metadata.getKnowledgeId()
        );
        requireDocumentKnowledge(metadata.getKnowledgeId());
        ImportCallerContext caller =
            new ImportCallerContext(ImportCallerType.PUBLIC_API, token.getId());
        PublicDocumentImportDtos.SubmitResponse response =
            importFacade.submit(caller, metadata, files);
        audit(
            token,
            "API批量导入文档",
            servletRequest.getRequestURI(),
            Map.of(
                "knowledgeId", metadata.getKnowledgeId(),
                "taskId", response.getTaskId(),
                "totalCount", response.getTotalCount()
            )
        );
        return ResponseEntity.status(HttpStatus.ACCEPTED).body(Result.ok(response));
    }

    /**
     * 查询异步导入任务状态。
     *
     * @param apiKey 访问令牌
     * @param taskId 批次任务 ID
     * @param itemStatus 可选文件状态
     * @param pageNumber 页码
     * @param pageSize 每页数量
     * @param servletRequest Servlet 请求
     * @return 任务状态
     */
    @GetMapping("/status")
    public Result<PublicDocumentImportDtos.StatusResponse> status(
        @RequestHeader("ApiKey") String apiKey,
        @RequestParam BigInteger taskId,
        @RequestParam(required = false) String itemStatus,
        @RequestParam(defaultValue = "1") long pageNumber,
        @RequestParam(defaultValue = "20") long pageSize,
        HttpServletRequest servletRequest) {
        SysApiKey token = resolveAuthenticatedApiKey(servletRequest, apiKey);
        ImportCallerContext caller =
            new ImportCallerContext(ImportCallerType.PUBLIC_API, token.getId());
        BigInteger knowledgeId =
            importFacade.getOwnedKnowledgeId(caller, taskId);
        assertImportPermission(token, servletRequest.getRequestURI(), knowledgeId);
        PublicDocumentImportDtos.StatusResponse response =
            importFacade.getStatus(
                caller,
                taskId,
                itemStatus,
                pageNumber,
                pageSize
            );
        return Result.ok(response);
    }

    /**
     * 对异常任务执行断点重试。
     *
     * @param apiKey 访问令牌
     * @param request 重试请求
     * @param servletRequest Servlet 请求
     * @return 重试结果
     */
    @PostMapping(value = "/retry", consumes = MediaType.APPLICATION_JSON_VALUE)
    public Result<PublicDocumentImportDtos.RetryResponse> retry(
        @RequestHeader("ApiKey") String apiKey,
        @JsonBody PublicDocumentImportDtos.RetryRequest request,
        HttpServletRequest servletRequest) {
        SysApiKey token = resolveAuthenticatedApiKey(servletRequest, apiKey);
        ImportCallerContext caller =
            new ImportCallerContext(ImportCallerType.PUBLIC_API, token.getId());
        BigInteger taskId = request == null ? null : request.getTaskId();
        if (taskId == null) {
            throw new BusinessException("taskId 不能为空");
        }
        BigInteger knowledgeId =
            importFacade.getOwnedKnowledgeId(caller, taskId);
        assertImportPermission(token, servletRequest.getRequestURI(), knowledgeId);
        PublicDocumentImportDtos.RetryResponse response =
            importFacade.retry(caller, request);
        audit(
            token,
            "API重试批量导入任务",
            servletRequest.getRequestURI(),
            Map.of(
                "knowledgeId", knowledgeId,
                "taskId", response.getTaskId(),
                "retriedCount", response.getRetriedCount()
            )
        );
        return Result.ok(response);
    }

    /**
     * 解析并校验 metadata JSON Part。
     *
     * @param metadataPart metadata Part
     * @return 批量元数据
     */
    private PublicDocumentImportDtos.BatchMetadata parseMetadata(
        Part metadataPart) {
        if (metadataPart == null || metadataPart.getSize() <= 0) {
            throw new BusinessException("metadata 不能为空");
        }
        if (metadataPart.getSize() > MAX_METADATA_BYTES) {
            throw new BusinessException(413, 41304, "metadata 不能超过1MiB");
        }
        String contentType = metadataPart.getContentType();
        try {
            if (contentType == null
                || !MediaType.APPLICATION_JSON.includes(
                    MediaType.parseMediaType(contentType))) {
                throw new BusinessException(415, 41503,
                    "metadata Part 必须使用 application/json");
            }
        } catch (InvalidMediaTypeException error) {
            throw new BusinessException(415, 41503,
                "metadata Part Content-Type 无效", error);
        }
        try {
            String json = new String(
                metadataPart.getInputStream().readAllBytes(),
                StandardCharsets.UTF_8
            );
            PublicDocumentImportDtos.BatchMetadata metadata =
                JSON.parseObject(
                    json,
                    PublicDocumentImportDtos.BatchMetadata.class
                );
            if (metadata == null) {
                throw new BusinessException("metadata 不能为空");
            }
            return metadata;
        } catch (JSONException error) {
            throw new BusinessException(400, 40021,
                "metadata JSON 格式无效", error);
        } catch (java.io.IOException error) {
            throw new BusinessException(500, 50023,
                "读取 metadata 失败", error);
        }
    }

    /**
     * 断言访问令牌具有知识导入权限。
     *
     * @param token 访问令牌
     * @param requestUri 请求 URI
     * @param knowledgeId 知识库 ID
     */
    private void assertImportPermission(SysApiKey token,
                                        String requestUri,
                                        BigInteger knowledgeId) {
        permissionService.assertApiShare(
            token.getId(),
            requestUri,
            knowledgeId,
            KnowledgeApiPermissionScope.KNOWLEDGE_IMPORT.name()
        );
    }

    /**
     * 复用拦截器已经完成认证的访问令牌。
     *
     * @param request Servlet 请求
     * @param apiKey 访问令牌明文
     * @return 已认证访问令牌
     */
    private SysApiKey resolveAuthenticatedApiKey(
        HttpServletRequest request,
        String apiKey) {
        Object authenticated =
            request.getAttribute(
                PublicApiInterceptor.AUTHENTICATED_API_KEY_ATTRIBUTE
            );
        if (authenticated instanceof SysApiKey token) {
            return token;
        }
        // 兼容控制器单测和绕过 MVC 拦截器的内部直接调用。
        return sysApiKeyService.getSysApiKey(apiKey);
    }

    /**
     * 断言知识库存在且为文档类型。
     *
     * @param knowledgeId 知识库 ID
     */
    private void requireDocumentKnowledge(BigInteger knowledgeId) {
        DocumentCollection knowledge =
            knowledgeId == null ? null : documentCollectionService.getById(knowledgeId);
        if (knowledge == null) {
            throw new BusinessException(404, 404, "知识库不存在");
        }
        if (!knowledge.isDocumentCollection()) {
            throw new BusinessException("当前知识库类型不支持文档导入");
        }
    }

    /**
     * 记录不含令牌明文的 Public API 审计。
     *
     * @param token 访问令牌
     * @param actionName 操作名称
     * @param actionUrl 操作 URI
     * @param detail 业务详情
     */
    private void audit(SysApiKey token,
                       String actionName,
                       String actionUrl,
                       Map<String, Object> detail) {
        Map<String, Object> payload = new HashMap<>(detail);
        payload.put("apiKeyId", token.getId());
        payload.put("channel", "API");
        auditService.log(
            null,
            actionName,
            "KNOWLEDGE_API_SHARE_WRITE",
            actionUrl,
            payload
        );
    }
}
