package com.zt.plat.module.qms.resource.record.controller.admin.onlyOffice.service;

import cn.hutool.core.util.StrUtil;
import cn.hutool.jwt.JWT;
import com.zt.plat.framework.common.pojo.CommonResult;
import com.zt.plat.framework.security.core.util.SecurityFrameworkUtils;
import com.zt.plat.module.infra.api.businessfile.BusinessFileApi;
import com.zt.plat.module.infra.api.file.FileApi;
import com.zt.plat.module.infra.api.file.dto.FileRespDTO;
import com.zt.plat.module.qms.resource.record.controller.admin.onlyOffice.controller.vo.OnlyOfficeEditorConfigRespVO;
import com.zt.plat.module.qms.resource.record.dal.dataobject.recordfileattachment.RecordFileAttachmentDO;
import com.zt.plat.module.qms.resource.record.service.recordfileattachment.RecordFileAttachmentService;
import com.zt.plat.module.system.api.user.AdminUserApi;
import com.zt.plat.module.system.api.user.dto.AdminUserRespDTO;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

import java.io.OutputStream;

import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.zt.plat.module.infra.enums.ErrorCodeConstants.DOC_NOT_EXISTS;

@Service
@Validated
@Slf4j
public class OnlyOfficeServiceImpl implements OnlyOfficeService{

    @Resource
    private AdminUserApi adminUserApi;

    @Resource
    private RecordFileAttachmentService recordFileAttachmentService;

    private static final String TEXT_DOCUMENT_EXTENSIONS = "doc,docx,odt,rtf,txt,html,htm,mht,pdf,djvu,fb2,epub,xps";
    private static final String SPREADSHEET_EXTENSIONS = "xls,xlsx,ods,csv";
    private static final String PRESENTATION_EXTENSIONS = "ppt,pptx,odp";

    // 文档类型常量
    private static final String DOCUMENT_TYPE_TEXT = "text";
    private static final String DOCUMENT_TYPE_SPREADSHEET = "spreadsheet";
    private static final String DOCUMENT_TYPE_PRESENTATION = "presentation";

    @Value("${onlyoffice.base-url:http://localhost:8085}")
    private String onlyOfficeBaseUrl;
    @Value("${onlyoffice.callback-base-url:http://host.docker.internal:48080}")
    private String onlyOfficeCallbackBaseUrl;
    @Value("${onlyoffice.jwt-secret:P@ssword25}")
    private String onlyOfficeJwtSecret;


    private String getDocumentType(String fileExtension) {
        if (StrUtil.isEmpty(fileExtension)) {
            return DOCUMENT_TYPE_TEXT;
        }

        String ext = fileExtension.toLowerCase();

        // 文本文档类型
        if (TEXT_DOCUMENT_EXTENSIONS.contains(ext)) {
            return DOCUMENT_TYPE_TEXT;
        }

        // 电子表格类型
        if (SPREADSHEET_EXTENSIONS.contains(ext)) {
            return DOCUMENT_TYPE_SPREADSHEET;
        }

        // 演示文稿类型
        if (PRESENTATION_EXTENSIONS.contains(ext)) {
            return DOCUMENT_TYPE_PRESENTATION;
        }

        // 默认返回text类型
        return DOCUMENT_TYPE_TEXT;
    }

    public String generateFileToken(Long fileId) {
        String fileToken = "";
        if (StrUtil.isNotBlank(onlyOfficeJwtSecret)) {
            // 创建文件访问token，包含fileId和过期时间（1小时后过期）
            long expTime = System.currentTimeMillis() / 1000 + 3600; // 1小时后过期
            fileToken = JWT.create()
                    .setPayload("fileId", fileId)
                    .setPayload("exp", expTime)
                    .setPayload("iat", System.currentTimeMillis() / 1000)
                    .setKey(onlyOfficeJwtSecret.getBytes())
                    .sign();
        }
        return fileToken;
    }

    @Resource
    private FileApi fileApi;

    @Override
    public OnlyOfficeEditorConfigRespVO getEditorConfig(Long id) {
//        RecordFileAttachmentDO recordFileAttachment = recordFileAttachmentService.getRecordFileAttachment(id);
//        if (recordFileAttachment == null) {
//            CommonResult.error(DOC_NOT_EXISTS);
//            return null;
//        }
        Long userId = SecurityFrameworkUtils.getLoginUserId();
        OnlyOfficeEditorConfigRespVO resp = new OnlyOfficeEditorConfigRespVO();

        CommonResult<FileRespDTO> file = fileApi.getFile(id);
        log.info("file:{}",file);
        if (file.getData() == null) throw exception(DOC_NOT_EXISTS);
        FileRespDTO data = file.getData();

        // 根据文件类型动态设置文档类型
        String documentType = getDocumentType(data.getType());
        resp.setDocumentType(documentType);

        OnlyOfficeEditorConfigRespVO.DocumentConfig document = new OnlyOfficeEditorConfigRespVO.DocumentConfig();
        // 获取文件类型，通过后缀名
        String fileName = data.getName();
        String fileType = getFileSuffix(fileName);
        document.setFileType(fileType);

        // 关键修复：使用基于文档版本的稳定key策略
        // 只有当文档内容真正发生版本变更时，key才会改变
        String documentKey = id + "_v" + System.currentTimeMillis();
        document.setKey(documentKey);
        document.setTitle(fileName);

//        log.info("生成文档key: docId={}, key={}, latestVersionId={}, userId={}",
//                doc.getId(), documentKey, doc.getLatestVersionId(), userId);

        // 使用新的接口生成文件 URL TODO
        String fileUrl = "";
        if (data.getId() != null) {
            // 生成文件访问的JWT token
            String fileToken = generateFileToken(data.getId());

            // 生成通过新接口访问文件的URL，包含JWT token
            fileUrl = StrUtil.removeSuffix(onlyOfficeCallbackBaseUrl, "/") + "/admin-api/qms/common/only-office/file-content?fileId=" + data.getId();
            if (StrUtil.isNotBlank(fileToken)) {
                fileUrl += "&token=" + fileToken;
            }
        }
        document.setUrl(fileUrl);


        OnlyOfficeEditorConfigRespVO.Permissions permissions = new OnlyOfficeEditorConfigRespVO.Permissions();
        // TODO 权限
//        permissions.setEdit(hasPermission(id, userId, DocPermissionTypeEnum.EDIT.getType()) || hasPermission(id, userId, DocPermissionTypeEnum.MANAGE.getType()));
        permissions.setEdit(true);
        permissions.setDownload(true);
        permissions.setPrint(true);
        document.setPermissions(permissions);
        resp.setDocument(document);

        OnlyOfficeEditorConfigRespVO.EditorConfig editorConfig = new OnlyOfficeEditorConfigRespVO.EditorConfig();
        // 在回调URL中添加触发用户ID，确保回调时能准确记录操作用户
        String callbackUrl = StrUtil.removeSuffix(onlyOfficeCallbackBaseUrl, "/") +
                "/admin-api/qms/common/only-office/callback?fileId=" + id + "&userId=" + userId;
        editorConfig.setCallbackUrl(callbackUrl);
        editorConfig.setLang("zh-CN");
        editorConfig.setMode(permissions.getEdit() ? "edit" : "view");

        // 重要：设置正确的用户信息以支持多人协作
        OnlyOfficeEditorConfigRespVO.User user = new OnlyOfficeEditorConfigRespVO.User();
        user.setId(String.valueOf(userId));

        // 获取真实用户姓名
        String userName = "User" + userId; // 默认值
        try {
            AdminUserRespDTO userInfo = adminUserApi.getUser(userId).getCheckedData();
            if (userInfo != null && StrUtil.isNotBlank(userInfo.getNickname())) {
                userName = userInfo.getNickname();
            }
        } catch (Exception e) {
            log.warn("获取用户姓名失败，使用默认值，用户ID: {}", userId, e);
        }
        user.setName(userName);
        editorConfig.setUser(user);

        // 添加协作配置
        OnlyOfficeEditorConfigRespVO.CoEditing coEditing = new OnlyOfficeEditorConfigRespVO.CoEditing();
        coEditing.setMode("fast"); // 快速协作模式
        coEditing.setChange(true); // 允许实时变更
        editorConfig.setCoEditing(coEditing);

        resp.setEditorConfig(editorConfig);
        resp.setHeight("100%");
        resp.setDocumentServerUrl(onlyOfficeBaseUrl);

        // 生成 token（可选 JWT）
        if (StrUtil.isNotBlank(onlyOfficeJwtSecret)) {
            // OnlyOffice 标准 JWT payload 格式
            String token = JWT.create()
                    .setPayload("document", resp.getDocument())
                    .setPayload("documentType", resp.getDocumentType())
                    .setPayload("editorConfig", resp.getEditorConfig())
                    .setPayload("height", resp.getHeight())
                    .setKey(onlyOfficeJwtSecret.getBytes())
                    .sign();
            resp.setToken(token);
        } else {
            // 如果没有 JWT secret，不设置 token，让 OnlyOffice 在无验证模式工作
            resp.setToken(null);
        }
        return resp;
    }
    @Override
    public void getDocFileContent(Long fileId, String token, HttpServletResponse response) {
        try {
            // 验证JWT token
            if (StrUtil.isBlank(token)) {
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                return;
            }

            // 验证JWT签名和有效性
            if (StrUtil.isNotBlank(onlyOfficeJwtSecret)) {
                try {
                    JWT jwt = JWT.of(token).setKey(onlyOfficeJwtSecret.getBytes());
                    if (!jwt.verify()) {
                        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                        return;
                    }

                    // 验证token中的fileId是否匹配
                    Object tokenFileId = jwt.getPayload("fileId");
                    if (tokenFileId == null || !fileId.equals(Long.valueOf(tokenFileId.toString()))) {
                        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
                        return;
                    }

                    // 验证token是否过期（可选，如果token中包含过期时间）
                    Object expTime = jwt.getPayload("exp");
                    if (expTime != null) {
                        long expTimestamp = Long.parseLong(expTime.toString());
                        if (System.currentTimeMillis() / 1000 > expTimestamp) {
                            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                            return;
                        }
                    }
                } catch (Exception e) {
                    log.warn("JWT验证失败: token={}", token, e);
                    response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                    return;
                }
            }

            // 获取文件信息
            CommonResult<FileRespDTO> fileInfo = fileApi.getFileInfo(fileId);
            FileRespDTO file = fileInfo.getData();
            if (file == null) {
                response.setStatus(HttpServletResponse.SC_NOT_FOUND);
                return;
            }

            // 获取文件内容：优先从 content 字段取，若为 null（文件存储在外网OSS时）则通过 URL 下载
            byte[] content = file.getContent();
            if (content == null) {
                // 文件存储在外网（OSS/MinIO等），通过 URL 下载文件内容
                String fileUrl = file.getUrl();
                if (StrUtil.isBlank(fileUrl)) {
                    log.error("文件内容和URL均为空，无法提供文件: fileId={}", fileId);
                    response.setStatus(HttpServletResponse.SC_NOT_FOUND);
                    return;
                }
                log.info("文件content为空，通过URL下载: fileId={}, url={}", fileId, fileUrl);
                try {
                    java.net.URL url = new java.net.URL(fileUrl);
                    java.net.HttpURLConnection conn = (java.net.HttpURLConnection) url.openConnection();
                    conn.setConnectTimeout(10000);
                    conn.setReadTimeout(30000);
                    conn.setRequestMethod("GET");
                    int statusCode = conn.getResponseCode();
                    if (statusCode != 200) {
                        log.error("从外网URL下载文件失败: fileId={}, url={}, httpStatus={}", fileId, fileUrl, statusCode);
                        response.setStatus(HttpServletResponse.SC_BAD_GATEWAY);
                        return;
                    }
                    // 设置响应头
                    response.setContentType(file.getType() != null ? file.getType() : "application/octet-stream");
                    response.setHeader("Content-Disposition", "inline; filename=\"" + file.getName() + "\"");
                    response.setHeader("Access-Control-Allow-Origin", "*");
                    response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
                    response.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With");
                    response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
                    response.setHeader("Pragma", "no-cache");
                    response.setHeader("Expires", "0");
                    // 流式透传，避免大文件OOM
                    try (java.io.InputStream in = conn.getInputStream();
                         OutputStream os = response.getOutputStream()) {
                        byte[] buffer = new byte[8192];
                        int bytesRead;
                        while ((bytesRead = in.read(buffer)) != -1) {
                            os.write(buffer, 0, bytesRead);
                        }
                        os.flush();
                    }
                    return;
                } catch (Exception e) {
                    log.error("从外网URL下载文件异常: fileId={}, url={}", fileId, fileUrl, e);
                    response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
                    return;
                }
            }

            // 设置响应头
            response.setContentType(file.getType() != null ? file.getType() : "application/octet-stream");
            response.setHeader("Content-Disposition", "inline; filename=\"" + file.getName() + "\"");
            // 添加跨域头，允许 OnlyOffice 访问
            response.setHeader("Access-Control-Allow-Origin", "*");
            response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
            response.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With");
            response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
            response.setHeader("Pragma", "no-cache");
            response.setHeader("Expires", "0");

            // 写入响应
            try (OutputStream os = response.getOutputStream()) {
                os.write(content);
                os.flush();
            }
        } catch (Exception e) {
            log.error("获取文档文件内容失败: fileId={}", fileId, e);
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        }
    }

    public String getFileSuffix(String fileName){
        //从文件名中最后一个点截取，例：1111.111.222.docx,截取 docx
        if (fileName == null || fileName.isEmpty()) {
            return "";
        }
        int lastDotIndex = fileName.lastIndexOf('.');
        if (lastDotIndex == -1 || lastDotIndex == fileName.length() - 1) {
            return "";
        }
        return fileName.substring(lastDotIndex + 1).toLowerCase();
    }


}
