package tech.easyflow.ai.easyagentsflow.upload;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import tech.easyflow.common.filestorage.FileStorageService;
import tech.easyflow.common.filestorage.FileStorageWriteHandle;

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 读取经过 Public Workflow API 上传记录验证的临时文件。
 *
 * <p>外部文件描述只提供公开读取路径。仅当路径中的随机上传 ID、Redis 上传记录、
 * 完整文件 URL 和可恢复存储句柄全部匹配时，才允许绕过公网 URL 限制并直接读取物理对象。</p>
 */
@Component
public class WorkflowApiUploadedFileReader {

    private static final Logger LOG = LoggerFactory.getLogger(
            WorkflowApiUploadedFileReader.class);
    private static final String STORAGE_PATH_PREFIX = "workflow-api-upload/";
    private static final Pattern MANAGED_PATH_PATTERN = Pattern.compile(
            "(?:^|/)workflow-api-upload/([0-9a-f]{32})/([^/]+)$");

    private final WorkflowApiUploadStore uploadStore;
    private final FileStorageService fileStorageService;

    /**
     * 创建工作流 API 上传文件读取器。
     *
     * @param uploadStore 临时上传记录存储
     * @param fileStorageService 默认文件存储路由
     */
    public WorkflowApiUploadedFileReader(
            WorkflowApiUploadStore uploadStore,
            @Qualifier("default") FileStorageService fileStorageService) {
        this.uploadStore = uploadStore;
        this.fileStorageService = fileStorageService;
    }

    /**
     * 在路径属于受管工作流上传文件时校验记录并打开物理对象。
     *
     * @param filePath 工作流文件描述中的完整读取路径
     * @return 受管文件流；普通文件路径或普通远端 URL 返回空
     * @throws IOException 上传记录已失效、引用不匹配或物理对象无法读取时抛出
     */
    public Optional<InputStream> openVerified(String filePath) throws IOException {
        ManagedPath managedPath = parseManagedPath(filePath).orElse(null);
        if (managedPath == null) {
            return Optional.empty();
        }
        WorkflowApiUploadRecord record = uploadStore.find(managedPath.uploadId())
                .orElseThrow(() -> new IOException("工作流上传文件已失效，请重新上传"));
        if (!managedPath.uploadId().equals(record.getUploadId())) {
            throw new IOException("工作流上传文件引用与上传记录不匹配");
        }
        WorkflowApiStoredFile storedFile = record.getStoredFiles().stream()
                .filter(file -> file != null && filePath.equals(file.filePath()))
                .findFirst()
                .orElseThrow(() -> new IOException("工作流上传文件引用与上传记录不匹配"));

        final FileStorageWriteHandle handle;
        try {
            handle = FileStorageWriteHandle.decodeLocator(storedFile.storageLocator());
        } catch (IllegalArgumentException exception) {
            throw new IOException("工作流上传文件存储定位符无效", exception);
        }
        String expectedStoragePath = STORAGE_PATH_PREFIX + managedPath.uploadId() + "/";
        if (!expectedStoragePath.equals(handle.getPath())
                || !managedPath.filename().equals(handle.getFilename())) {
            throw new IOException("工作流上传文件存储定位与上传请求不匹配");
        }
        try {
            return Optional.of(fileStorageService.readRecoverable(handle));
        } catch (RuntimeException exception) {
            LOG.error(
                    "读取工作流 API 上传文件失败, uploadId={}, requestId={}, executeId={}",
                    record.getUploadId(),
                    record.getRequestId(),
                    record.getExecuteId(),
                    exception);
            throw new IOException("读取工作流上传文件失败", exception);
        }
    }

    /**
     * 判断路径结构是否属于系统生成的工作流 API 上传目录。
     *
     * <p>该判断只用于选择 I/O 隔离通道，不能替代 {@link #openVerified(String)} 的授权校验。</p>
     *
     * @param filePath 文件读取路径
     * @return 路径结构匹配时返回 true
     */
    public boolean isManagedPathCandidate(String filePath) {
        return parseManagedPath(filePath).isPresent();
    }

    /**
     * 从 URL 或相对路径中解析受管上传 ID 与固定文件名。
     *
     * @param filePath 原始文件路径
     * @return 受管路径信息
     */
    private Optional<ManagedPath> parseManagedPath(String filePath) {
        if (!StringUtils.hasText(filePath)) {
            return Optional.empty();
        }
        final String path;
        try {
            URI uri = URI.create(filePath);
            path = uri.getPath();
        } catch (IllegalArgumentException exception) {
            return Optional.empty();
        }
        if (!StringUtils.hasText(path)) {
            return Optional.empty();
        }
        Matcher matcher = MANAGED_PATH_PATTERN.matcher(path);
        if (!matcher.find()) {
            return Optional.empty();
        }
        return Optional.of(new ManagedPath(matcher.group(1), matcher.group(2)));
    }

    /**
     * 系统受管上传路径中的可信定位片段。
     *
     * @param uploadId 随机内部上传 ID
     * @param filename 系统生成的存储文件名
     */
    private record ManagedPath(String uploadId, String filename) {
    }
}
