package tech.easyflow.common.filestorage;

import tech.easyflow.common.filestorage.xFileStorage.StorageConfig;
import tech.easyflow.common.util.SpringContextUtil;
import tech.easyflow.common.filestorage.impl.LocalFileStorageServiceImpl;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Objects;
import java.util.function.Function;
import java.util.function.Supplier;

/**
 * 根据平台配置路由文件存储操作的统一入口。
 *
 * <p>旧版操作每次使用当前后端；可恢复操作在 prepare 阶段固化后端，并在后续写入、检查及
 * 删除时严格按照句柄路由，避免配置切换后误操作另一个后端。</p>
 */
@Component("default")
public class FileStorageManager implements FileStorageService {

    /** 当前存储后端名称提供器。 */
    private final Supplier<String> backendSupplier;
    /** 按 bean 名称解析存储后端的函数。 */
    private final Function<String, FileStorageService> serviceResolver;

    /**
     * 创建使用 Spring 上下文与当前存储配置的管理器。
     */
    public FileStorageManager() {
        this(FileStorageManager::configuredBackend, FileStorageManager::springService);
    }

    /**
     * 创建使用指定路由提供器的管理器，供隔离测试使用。
     *
     * @param backendSupplier 当前存储后端名称提供器
     * @param serviceResolver 按名称解析存储服务的函数
     */
    FileStorageManager(Supplier<String> backendSupplier,
                       Function<String, FileStorageService> serviceResolver) {
        this.backendSupplier = Objects.requireNonNull(backendSupplier, "backendSupplier 不能为空");
        this.serviceResolver = Objects.requireNonNull(serviceResolver, "serviceResolver 不能为空");
    }

    /**
     * 使用当前后端保存文件。
     *
     * @param file 上传文件
     * @return 文件 URL
     */
    @Override
    public String save(MultipartFile file) {
        return currentService().save(file);
    }

    /**
     * 使用当前后端及指定前置目录保存文件。
     *
     * @param file 上传文件
     * @param prePath 前置目录
     * @return 文件 URL
     */
    @Override
    public String save(MultipartFile file, String prePath) {
        return currentService().save(file, prePath);
    }

    /**
     * 使用当前后端删除旧版 URL 或路径。
     *
     * @param path 文件 URL 或路径
     */
    @Override
    public void delete(String path) {
        currentService().delete(path);
    }

    /**
     * 使用当前后端保存本地文件。
     *
     * @param file 本地文件
     * @param prePath 前置目录
     * @return 文件 URL
     */
    @Override
    public String save(File file, String prePath) {
        return currentService().save(file, prePath);
    }

    /**
     * 使用当前后端打开文件流。
     *
     * @param path 文件 URL 或路径
     * @return 文件输入流
     * @throws IOException 无法读取文件时抛出
     */
    @Override
    public InputStream readStream(String path) throws IOException {
        return currentService().readStream(path);
    }

    /**
     * 使用当前后端获取文件大小。
     *
     * @param path 文件 URL 或路径
     * @return 文件大小
     */
    @Override
    public long getFileSize(String path) {
        return currentService().getFileSize(path);
    }

    /**
     * 委托当前后端准备可恢复写句柄。
     *
     * @param path 相对目录
     * @param filename 固定文件名
     * @return 包含当前后端路由的句柄
     */
    @Override
    public FileStorageWriteHandle prepareRecoverableWrite(String path, String filename) {
        return currentService().prepareRecoverableWrite(path, filename);
    }

    /**
     * 严格按句柄中的后端完成精确写入。
     *
     * @param file 上传文件
     * @param handle 预先准备的句柄
     * @return 文件 URL 与恢复 locator
     */
    @Override
    public FileStorageWriteResult saveRecoverable(MultipartFile file, FileStorageWriteHandle handle) {
        return serviceForHandle(handle).saveRecoverable(file, handle);
    }

    /**
     * 严格按句柄中的后端打开物理对象读取流。
     *
     * @param handle 物理对象句柄
     * @return 文件输入流，由调用方关闭
     * @throws IOException 无法读取物理对象时抛出
     */
    @Override
    public InputStream readRecoverable(FileStorageWriteHandle handle) throws IOException {
        return serviceForHandle(handle).readRecoverable(handle);
    }

    /**
     * 严格按句柄中的后端精确删除物理对象。
     *
     * @param handle 物理对象句柄
     */
    @Override
    public void deleteRecoverable(FileStorageWriteHandle handle) {
        serviceForHandle(handle).deleteRecoverable(handle);
    }

    /**
     * 严格按句柄中的后端检查物理对象。
     *
     * @param handle 物理对象句柄
     * @return 物理对象存在时返回 true
     */
    @Override
    public boolean existsRecoverable(FileStorageWriteHandle handle) {
        return serviceForHandle(handle).existsRecoverable(handle);
    }

    /**
     * 解析当前配置对应的文件存储服务。
     *
     * @return 当前文件存储服务
     */
    private FileStorageService currentService() {
        return serviceForBackend(normalizeBackend(backendSupplier.get()));
    }

    /**
     * 从句柄解析固定文件存储服务。
     *
     * @param handle 文件存储句柄
     * @return 句柄指定的文件存储服务
     */
    private FileStorageService serviceForHandle(FileStorageWriteHandle handle) {
        if (handle == null) {
            throw new IllegalArgumentException("文件存储写句柄不能为空");
        }
        return serviceForBackend(handle.getBackend());
    }

    /**
     * 按已固化的后端名称解析服务，禁止回路由到管理器自身。
     *
     * @param backend 后端 bean 名称
     * @return 具体文件存储服务
     */
    private FileStorageService serviceForBackend(String backend) {
        if ("default".equals(backend)) {
            throw new IllegalArgumentException("恢复句柄不能路由到 default 管理器");
        }
        FileStorageService service = serviceResolver.apply(backend);
        if (service == null || service == this) {
            throw new IllegalStateException("文件存储后端不可用: " + backend);
        }
        return service;
    }

    /**
     * 读取并规范化当前配置中的后端名称。
     *
     * @return 后端 bean 名称
     */
    private static String configuredBackend() {
        String type = StorageConfig.getInstance().getType();
        return normalizeBackend(type);
    }

    /**
     * 将空配置映射到本地后端。
     *
     * @param backend 配置值
     * @return 非空后端 bean 名称
     */
    private static String normalizeBackend(String backend) {
        return StringUtils.hasText(backend) ? backend.trim() : "local";
    }

    /**
     * 从 Spring 上下文按名称取得具体文件存储服务。
     *
     * @param backend 后端 bean 名称
     * @return 具体服务
     */
    private static FileStorageService springService(String backend) {
        if ("local".equals(backend)) {
            return SpringContextUtil.getBean(LocalFileStorageServiceImpl.class);
        }
        return SpringContextUtil.getBean(backend, FileStorageService.class);
    }
}
