package tech.easyflow.admin.controller.skill;

import cn.dev33.satoken.annotation.SaCheckPermission;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.MediaType;
import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import tech.easyflow.admin.controller.ai.support.AiResourceCreatorNameSupport;
import tech.easyflow.ai.enums.PublishStatus;
import tech.easyflow.approval.entity.vo.ApprovalActionResult;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.web.controller.BaseCurdController;
import tech.easyflow.common.web.jsonbody.JsonBody;
import tech.easyflow.skill.entity.Skill;
import tech.easyflow.skill.file.SkillFileContent;
import tech.easyflow.skill.file.SkillFileNode;
import tech.easyflow.skill.file.SkillFileSaveRequest;
import tech.easyflow.skill.file.SkillFileService;
import tech.easyflow.skill.imports.SkillExportService;
import tech.easyflow.skill.imports.SkillImportPreview;
import tech.easyflow.skill.imports.SkillImportService;
import tech.easyflow.skill.publish.SkillPublishAppService;
import tech.easyflow.skill.service.SkillApprovalStateService;
import tech.easyflow.skill.service.SkillService;
import tech.easyflow.system.entity.vo.RoleCategoryAccessSnapshot;
import tech.easyflow.system.enums.CategoryResourceType;
import tech.easyflow.system.enums.ResourceAction;
import tech.easyflow.system.service.CategoryPermissionService;
import tech.easyflow.system.service.ResourceAccessService;

import javax.annotation.Resource;
import java.io.InputStream;
import java.io.Serializable;
import java.math.BigInteger;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import static tech.easyflow.skill.entity.table.SkillTableDef.SKILL;

/**
 * Skill 管理端控制器。
 */
@RestController
@RequestMapping("/api/v1/skill")
public class SkillController extends BaseCurdController<SkillService, Skill> {

    @Resource
    private SkillApprovalStateService skillApprovalStateService;
    @Resource
    private SkillPublishAppService skillPublishAppService;
    @Resource
    private SkillImportService skillImportService;
    @Resource
    private SkillExportService skillExportService;
    @Resource
    private SkillFileService skillFileService;
    @Resource
    private ResourceAccessService resourceAccessService;
    @Resource
    private CategoryPermissionService categoryPermissionService;
    @Resource
    private AiResourceCreatorNameSupport aiResourceCreatorNameSupport;

    /**
     * 创建 Skill 控制器。
     *
     * @param service Skill 服务
     */
    public SkillController(SkillService service) {
        super(service);
    }

    /**
     * 获取 Skill 详情。
     *
     * @param id Skill ID
     * @return Skill 详情
     */
    @GetMapping("/getDetail")
    public Result<Skill> getDetail(BigInteger id) {
        Skill skill = service.getDetail(id);
        skillApprovalStateService.fillSkillApprovalState(skill);
        return Result.ok(skill);
    }

    /**
     * 保存 Skill 草稿。
     *
     * @param skill Skill 草稿
     * @return 保存后的 Skill
     */
    @Override
    @PostMapping("save")
    public Result<?> save(@JsonBody Skill skill) {
        return Result.ok(service.saveDraft(skill));
    }

    /**
     * 更新 Skill 草稿。
     *
     * @param skill Skill 草稿
     * @return 保存后的 Skill
     */
    @Override
    @PostMapping("update")
    public Result<?> update(@JsonBody Skill skill) {
        return Result.ok(service.updateDraft(skill));
    }

    /**
     * 预览 zip 导入结果。
     *
     * @param file zip 文件
     * @return 导入预览
     */
    @PostMapping(value = "/import/preview", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    @SaCheckPermission("/api/v1/skill/save")
    public Result<SkillImportPreview> importPreview(MultipartFile file) throws Exception {
        return Result.ok(skillImportService.preview(file.getInputStream()));
    }

    /**
     * 确认导入 zip。
     *
     * @param file zip 文件
     * @param categoryId 分类 ID
     * @param overwriteDraft 是否覆盖草稿
     * @return 导入后的 Skill 列表
     */
    @PostMapping(value = "/import/confirm", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    @SaCheckPermission("/api/v1/skill/save")
    public Result<List<Skill>> importConfirm(MultipartFile file, BigInteger categoryId, Boolean overwriteDraft) throws Exception {
        return Result.ok(skillImportService.importZip(file.getInputStream(), categoryId, Boolean.TRUE.equals(overwriteDraft)));
    }

    /**
     * 导出 Skill zip。
     *
     * @param ids Skill ID 集合
     * @param response HTTP 响应
     */
    @PostMapping("/export")
    public void export(@JsonBody(value = "ids", required = true) List<BigInteger> ids, HttpServletResponse response) throws Exception {
        response.setContentType("application/zip");
        response.setHeader("Content-Disposition", "attachment; filename=\"" + URLEncoder.encode("skills.zip", StandardCharsets.UTF_8) + "\"");
        skillExportService.exportZip(ids, response.getOutputStream());
    }

    /**
     * 获取 Skill 文件树。
     *
     * @param skillId Skill ID
     * @return 文件树
     */
    @GetMapping("/file/tree")
    public Result<List<SkillFileNode>> fileTree(BigInteger skillId) {
        return Result.ok(skillFileService.tree(skillId));
    }

    /**
     * 获取 Skill 文件内容。
     *
     * @param skillId Skill ID
     * @param path 逻辑路径
     * @return 文件内容
     */
    @GetMapping("/file/content")
    public Result<SkillFileContent> fileContent(BigInteger skillId, String path) {
        return Result.ok(skillFileService.getContent(skillId, path));
    }

    /**
     * 保存 Skill 文本文件。
     *
     * @param request 保存请求
     * @return 保存后的文件内容
     */
    @PostMapping("/file/save")
    @SaCheckPermission("/api/v1/skill/save")
    public Result<SkillFileContent> saveFile(@JsonBody SkillFileSaveRequest request) {
        return Result.ok(skillFileService.saveContent(request));
    }

    /**
     * 删除 Skill 逻辑文件。
     *
     * @param skillId Skill ID
     * @param path 逻辑路径
     * @return 操作结果
     */
    @PostMapping("/file/delete")
    @SaCheckPermission("/api/v1/skill/save")
    public Result<Void> deleteFile(@JsonBody(value = "skillId", required = true) BigInteger skillId,
                                   @JsonBody(value = "path", required = true) String path) {
        skillFileService.deleteFile(skillId, path);
        return Result.ok();
    }

    /**
     * 上传 Skill asset。
     *
     * @param skillId Skill ID
     * @param path 逻辑路径
     * @param file 上传文件
     * @return asset 内容
     */
    @PostMapping(value = "/file/asset/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    @SaCheckPermission("/api/v1/skill/save")
    public Result<SkillFileContent> uploadAsset(BigInteger skillId, String path, MultipartFile file) {
        return Result.ok(skillFileService.uploadAsset(skillId, path, file));
    }

    /**
     * 下载或预览 Skill asset。
     *
     * @param skillId Skill ID
     * @param path asset 逻辑路径
     * @param response HTTP 响应
     */
    @GetMapping("/file/asset")
    public void asset(BigInteger skillId, String path, HttpServletResponse response) throws Exception {
        SkillFileContent content = skillFileService.getContent(skillId, path);
        response.setContentType(content.getMediaType() == null ? MediaType.APPLICATION_OCTET_STREAM_VALUE : content.getMediaType());
        response.setHeader("Content-Disposition", "inline; filename=\"" + URLEncoder.encode(fileName(path), StandardCharsets.UTF_8) + "\"");
        try (InputStream inputStream = skillFileService.openAsset(skillId, path)) {
            StreamUtils.copy(inputStream, response.getOutputStream());
        }
    }

    /**
     * 提交发布审批。
     *
     * @param id Skill ID
     * @return 审批实例 ID
     */
    @PostMapping("/submitPublishApproval")
    @SaCheckPermission("/api/v1/skill/save")
    public Result<BigInteger> submitPublishApproval(@JsonBody("id") BigInteger id) {
        return buildApprovalActionResult(skillPublishAppService.submitPublishApproval(id), "已提交发布审批", "已直接发布");
    }

    /**
     * 提交下线审批。
     *
     * @param id Skill ID
     * @return 审批实例 ID
     */
    @PostMapping("/submitOfflineApproval")
    @SaCheckPermission("/api/v1/skill/save")
    public Result<BigInteger> submitOfflineApproval(@JsonBody("id") BigInteger id) {
        return buildApprovalActionResult(skillPublishAppService.submitOfflineApproval(id), "已提交下线审批", "已直接下线");
    }

    /**
     * 提交删除审批。
     *
     * @param id Skill ID
     * @return 审批实例 ID
     */
    @PostMapping("/submitDeleteApproval")
    @SaCheckPermission("/api/v1/skill/remove")
    public Result<BigInteger> submitDeleteApproval(@JsonBody("id") BigInteger id) {
        return buildApprovalActionResult(skillPublishAppService.submitDeleteApproval(id), "已提交删除审批", "已直接删除");
    }

    @Override
    protected Result<?> onRemoveBefore(Collection<Serializable> ids) {
        for (Serializable id : ids) {
            Skill skill = service.getById(String.valueOf(id));
            if (skill != null) {
                resourceAccessService.assertAccess(CategoryResourceType.SKILL, skill, ResourceAction.MANAGE, "无权限删除该 Skill");
            }
        }
        return super.onRemoveBefore(ids);
    }

    /**
     * 查询 Skill 分页。
     *
     * @param page 分页参数
     * @param queryWrapper 查询条件
     * @return Skill 分页
     */
    @Override
    protected Page<Skill> queryPage(Page<Skill> page, QueryWrapper queryWrapper) {
        if (!applyCategoryPermission(queryWrapper)) {
            return new Page<>(Collections.emptyList(), page.getPageNumber(), page.getPageSize(), 0L);
        }
        applyPublishedOnlyFilter(queryWrapper);
        Page<Skill> result = super.queryPage(page, queryWrapper);
        if (isPublishedOnlyRequest()) {
            result.setRecords(result.getRecords().stream().map(skill -> service.fromSnapshot(skill.getPublishedSnapshotJson())).toList());
        }
        skillApprovalStateService.fillSkillApprovalState(result.getRecords());
        aiResourceCreatorNameSupport.fillSkillCreatorNames(result.getRecords());
        return result;
    }

    private boolean applyCategoryPermission(QueryWrapper queryWrapper) {
        RoleCategoryAccessSnapshot access = categoryPermissionService.getCurrentAccess(CategoryResourceType.SKILL.getCode());
        if (!access.isRestricted()) {
            return true;
        }
        if (access.getCategoryIds().isEmpty()) {
            queryWrapper.eq(Skill::getCreatedBy, access.getAccountId());
            return true;
        }
        queryWrapper.and(SKILL.CREATED_BY.eq(access.getAccountId()).or(SKILL.CATEGORY_ID.in(access.getCategoryIds())));
        return true;
    }

    private void applyPublishedOnlyFilter(QueryWrapper queryWrapper) {
        if (isPublishedOnlyRequest()) {
            queryWrapper.eq("publish_status", PublishStatus.PUBLISHED.getCode());
        }
    }

    private boolean isPublishedOnlyRequest() {
        HttpServletRequest request = currentRequest();
        if (request == null) {
            return false;
        }
        return "true".equalsIgnoreCase(request.getParameter("publishedOnly"));
    }

    private HttpServletRequest currentRequest() {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes == null) {
            return null;
        }
        return attributes.getRequest();
    }

    private Result<BigInteger> buildApprovalActionResult(ApprovalActionResult actionResult,
                                                         String approvalMessage,
                                                         String directMessage) {
        return Result.ok(actionResult.isApprovalRequired() ? approvalMessage : directMessage, actionResult.getInstanceId());
    }

    private String fileName(String path) {
        if (path == null || path.isBlank()) {
            return "asset";
        }
        int index = path.lastIndexOf('/');
        return index < 0 ? path : path.substring(index + 1);
    }
}
