package tech.easyflow.common.filestorage;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.Locale;
import java.util.Objects;
import java.util.regex.Pattern;

/**
 * 描述一次可恢复文件写入的不可变物理定位信息。
 *
 * <p>句柄在上传前生成，随后可编码为有版本的 Base64URL locator 持久化。locator
 * 只承担稳定、安全的结构化传输与损坏检测，不是访问凭证，也不提供防伪能力；调用方不得
 * 接受未经授权的外部 locator。</p>
 */
public final class FileStorageWriteHandle {

    /** locator 文本前缀，其中包含当前编码版本。 */
    private static final String LOCATOR_PREFIX = "efsw1.";
    /** 二进制编码版本。 */
    private static final int BINARY_VERSION = 1;
    /** SHA-256 校验值长度。 */
    private static final int CHECKSUM_BYTES = 32;
    /** locator 最大字符数，与数据库 storage_locator VARCHAR(2048) 契约一致。 */
    private static final int MAX_LOCATOR_CHARS = 2_048;
    /** 后端名称最大 UTF-8 字节数。 */
    private static final int MAX_BACKEND_BYTES = 64;
    /** 平台名称最大 UTF-8 字节数。 */
    private static final int MAX_PLATFORM_BYTES = 128;
    /** 基础路径最大 UTF-8 字节数。 */
    private static final int MAX_BASE_PATH_BYTES = 4_096;
    /** 相对路径最大 UTF-8 字节数。 */
    private static final int MAX_PATH_BYTES = 2_048;
    /** 文件名最大 UTF-8 字节数。 */
    private static final int MAX_FILENAME_BYTES = 255;
    /** 可安全作为 Spring bean 名称及持久化路由键的标识符。 */
    private static final Pattern ROUTE_PATTERN = Pattern.compile("[A-Za-z0-9][A-Za-z0-9._-]*");
    /** Base64URL 无填充文本允许的字符。 */
    private static final Pattern BASE64_URL_PATTERN = Pattern.compile("[A-Za-z0-9_-]+");
    /** Windows 保留设备名，避免 locator 在跨平台恢复时产生歧义。 */
    private static final Pattern WINDOWS_RESERVED_NAME = Pattern.compile(
            "(?i)(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(?:\\..*)?");

    /** 负责处理该句柄的 EasyFlow 文件存储后端 bean 名称。 */
    private final String backend;
    /** x-file-storage 平台名称；非 x-file-storage 后端可为空。 */
    private final String platform;
    /** 准备写入时解析得到的持久基础路径或本地存储根目录。 */
    private final String basePath;
    /** 基础路径下的规范化相对目录，以斜杠结尾；根目录使用空字符串。 */
    private final String path;
    /** 目标对象的固定文件名。 */
    private final String filename;

    /**
     * 创建并严格校验一个文件存储写句柄。
     *
     * @param backend 存储后端路由名称
     * @param platform x-file-storage 平台名称，非该类后端可为空
     * @param basePath 持久基础路径或本地存储根目录
     * @param path 基础路径下的相对目录，可为空
     * @param filename 固定文件名
     * @throws IllegalArgumentException 任一字段为空、过长或包含不安全路径时抛出
     */
    public FileStorageWriteHandle(String backend,
                                  String platform,
                                  String basePath,
                                  String path,
                                  String filename) {
        this.backend = validateRoute("backend", backend, false, MAX_BACKEND_BYTES);
        this.platform = validateRoute("platform", platform, true, MAX_PLATFORM_BYTES);
        this.basePath = validateBasePath(basePath);
        this.path = normalizeRelativePath(path);
        this.filename = validatePathSegment("filename", filename, MAX_FILENAME_BYTES);
        if (buildLocator().length() > MAX_LOCATOR_CHARS) {
            throw new IllegalArgumentException("文件存储 locator 超过 2048 字符持久化限制");
        }
    }

    /**
     * 获取负责处理该句柄的存储后端路由名称。
     *
     * @return 存储后端 bean 名称
     */
    public String getBackend() {
        return backend;
    }

    /**
     * 获取 x-file-storage 平台名称。
     *
     * @return 平台名称，非 x-file-storage 后端时可为空字符串
     */
    public String getPlatform() {
        return platform;
    }

    /**
     * 获取准备写入时固化的基础路径。
     *
     * @return 基础路径或本地绝对根目录
     */
    public String getBasePath() {
        return basePath;
    }

    /**
     * 获取规范化相对目录。
     *
     * @return 空字符串或以斜杠结尾的相对目录
     */
    public String getPath() {
        return path;
    }

    /**
     * 获取固定文件名。
     *
     * @return 文件名
     */
    public String getFilename() {
        return filename;
    }

    /**
     * 将句柄编码为带版本、无填充且具有完整性校验的 Base64URL locator。
     *
     * @return 可安全持久化到文本字段的 locator
     * @throws IllegalStateException 当前 JVM 不支持 SHA-256 或编码失败时抛出
     */
    public String encodeLocator() {
        String locator = buildLocator();
        if (locator.length() > MAX_LOCATOR_CHARS) {
            throw new IllegalStateException("文件存储 locator 超过 2048 字符持久化限制");
        }
        return locator;
    }

    /**
     * 构造 locator 文本，长度检查由调用方在最终返回或构造校验阶段完成。
     *
     * @return locator 文本
     */
    private String buildLocator() {
        try {
            ByteArrayOutputStream bodyBuffer = new ByteArrayOutputStream();
            try (DataOutputStream output = new DataOutputStream(bodyBuffer)) {
                output.writeByte(BINARY_VERSION);
                writeString(output, backend);
                writeString(output, platform);
                writeString(output, basePath);
                writeString(output, path);
                writeString(output, filename);
            }
            byte[] body = bodyBuffer.toByteArray();
            byte[] checksum = sha256(body);
            ByteBuffer encoded = ByteBuffer.allocate(body.length + checksum.length);
            encoded.put(body).put(checksum);
            return LOCATOR_PREFIX + Base64.getUrlEncoder().withoutPadding().encodeToString(encoded.array());
        } catch (IOException exception) {
            throw new IllegalStateException("编码文件存储 locator 失败", exception);
        }
    }

    /**
     * 解码并严格校验一个文件存储 locator。
     *
     * @param locator 由 {@link #encodeLocator()} 生成的 locator
     * @return 不可变文件存储写句柄
     * @throws IllegalArgumentException locator 版本、编码、校验值或字段不合法时抛出
     */
    public static FileStorageWriteHandle decodeLocator(String locator) {
        if (locator == null || locator.length() <= LOCATOR_PREFIX.length()
                || locator.length() > MAX_LOCATOR_CHARS || !locator.startsWith(LOCATOR_PREFIX)) {
            throw new IllegalArgumentException("文件存储 locator 格式不正确");
        }
        String encoded = locator.substring(LOCATOR_PREFIX.length());
        if (!BASE64_URL_PATTERN.matcher(encoded).matches()) {
            throw new IllegalArgumentException("文件存储 locator 不是无填充 Base64URL 编码");
        }
        final byte[] bytes;
        try {
            bytes = Base64.getUrlDecoder().decode(encoded);
        } catch (IllegalArgumentException exception) {
            throw new IllegalArgumentException("文件存储 locator Base64URL 编码不正确", exception);
        }
        if (bytes.length <= CHECKSUM_BYTES + 1) {
            throw new IllegalArgumentException("文件存储 locator 数据不完整");
        }
        byte[] body = java.util.Arrays.copyOf(bytes, bytes.length - CHECKSUM_BYTES);
        byte[] checksum = java.util.Arrays.copyOfRange(bytes, body.length, bytes.length);
        if (!MessageDigest.isEqual(checksum, sha256(body))) {
            throw new IllegalArgumentException("文件存储 locator 完整性校验失败");
        }
        try (DataInputStream input = new DataInputStream(new ByteArrayInputStream(body))) {
            int version = input.readUnsignedByte();
            if (version != BINARY_VERSION) {
                throw new IllegalArgumentException("不支持的文件存储 locator 版本: " + version);
            }
            FileStorageWriteHandle handle = new FileStorageWriteHandle(
                    readString(input, "backend", MAX_BACKEND_BYTES),
                    readString(input, "platform", MAX_PLATFORM_BYTES),
                    readString(input, "basePath", MAX_BASE_PATH_BYTES),
                    readString(input, "path", MAX_PATH_BYTES),
                    readString(input, "filename", MAX_FILENAME_BYTES));
            if (input.available() != 0 || !handle.encodeLocator().equals(locator)) {
                throw new IllegalArgumentException("文件存储 locator 包含非规范数据");
            }
            return handle;
        } catch (IOException exception) {
            throw new IllegalArgumentException("文件存储 locator 数据不完整", exception);
        }
    }

    /**
     * 将字符串以长度前缀 UTF-8 格式写入 locator 载荷。
     *
     * @param output 目标数据流
     * @param value 字符串值
     * @throws IOException 写入失败时抛出
     */
    private static void writeString(DataOutputStream output, String value) throws IOException {
        byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
        output.writeInt(bytes.length);
        output.write(bytes);
    }

    /**
     * 从 locator 载荷读取一个有界、严格 UTF-8 字符串。
     *
     * @param input locator 数据流
     * @param field 字段名
     * @param maxBytes 最大 UTF-8 字节数
     * @return 解码字符串
     * @throws IOException 数据流不完整时抛出
     * @throws IllegalArgumentException 长度或 UTF-8 编码不合法时抛出
     */
    private static String readString(DataInputStream input, String field, int maxBytes) throws IOException {
        int length = input.readInt();
        if (length < 0 || length > maxBytes || length > input.available()) {
            throw new IllegalArgumentException(field + " 长度不正确");
        }
        byte[] bytes = input.readNBytes(length);
        try {
            return StandardCharsets.UTF_8.newDecoder()
                    .onMalformedInput(CodingErrorAction.REPORT)
                    .onUnmappableCharacter(CodingErrorAction.REPORT)
                    .decode(ByteBuffer.wrap(bytes))
                    .toString();
        } catch (CharacterCodingException exception) {
            throw new IllegalArgumentException(field + " 不是合法 UTF-8", exception);
        }
    }

    /**
     * 校验存储后端或平台路由标识。
     *
     * @param field 字段名
     * @param value 字段值
     * @param allowEmpty 是否允许空字符串
     * @param maxBytes 最大 UTF-8 字节数
     * @return 经校验的原值
     */
    private static String validateRoute(String field, String value, boolean allowEmpty, int maxBytes) {
        if (value == null || (!allowEmpty && value.isBlank())) {
            throw new IllegalArgumentException(field + " 不能为空");
        }
        if (value.isEmpty() && allowEmpty) {
            return value;
        }
        if (!value.equals(value.trim()) || utf8Length(value) > maxBytes || !ROUTE_PATTERN.matcher(value).matches()) {
            throw new IllegalArgumentException(field + " 不是合法路由标识");
        }
        return value;
    }

    /**
     * 校验句柄中的基础路径。
     *
     * @param value 基础路径
     * @return 经校验的原值
     */
    private static String validateBasePath(String value) {
        if (value == null || utf8Length(value) > MAX_BASE_PATH_BYTES || containsControlCharacter(value)) {
            throw new IllegalArgumentException("basePath 不合法或超过长度限制");
        }
        validateNoTraversalSegments(value, "basePath");
        return value;
    }

    /**
     * 规范化并校验相对目录。
     *
     * @param value 相对目录
     * @return 空字符串或以斜杠结尾的规范目录
     */
    private static String normalizeRelativePath(String value) {
        if (value == null || value.isEmpty()) {
            return "";
        }
        if (!value.equals(value.trim()) || value.startsWith("/") || value.startsWith("\\")
                || value.contains("\\") || value.contains("//") || containsControlCharacter(value)) {
            throw new IllegalArgumentException("path 必须是规范的安全相对路径");
        }
        String withoutTrailingSlash = value.endsWith("/") ? value.substring(0, value.length() - 1) : value;
        if (withoutTrailingSlash.isEmpty() || utf8Length(withoutTrailingSlash) + 1 > MAX_PATH_BYTES) {
            throw new IllegalArgumentException("path 不合法或超过长度限制");
        }
        String[] segments = withoutTrailingSlash.split("/", -1);
        for (String segment : segments) {
            validatePathSegment("path", segment, MAX_FILENAME_BYTES);
        }
        return withoutTrailingSlash + "/";
    }

    /**
     * 校验一个可移植的文件路径片段。
     *
     * @param field 字段名
     * @param value 路径片段
     * @param maxBytes 最大 UTF-8 字节数
     * @return 经校验的原值
     */
    private static String validatePathSegment(String field, String value, int maxBytes) {
        if (value == null || value.isBlank() || !value.equals(value.trim()) || ".".equals(value) || "..".equals(value)
                || utf8Length(value) > maxBytes || containsControlCharacter(value)
                || value.indexOf('/') >= 0 || value.indexOf('\\') >= 0
                || value.matches(".*[<>:\"|?*].*") || value.endsWith(".")
                || WINDOWS_RESERVED_NAME.matcher(value.toUpperCase(Locale.ROOT)).matches()) {
            throw new IllegalArgumentException(field + " 包含不安全路径片段");
        }
        return value;
    }

    /**
     * 拒绝基础路径中的当前目录和父目录片段。
     *
     * @param value 待检查路径
     * @param field 字段名
     */
    private static void validateNoTraversalSegments(String value, String field) {
        for (String segment : value.split("[/\\\\]", -1)) {
            if (".".equals(segment) || "..".equals(segment)) {
                throw new IllegalArgumentException(field + " 包含路径穿越片段");
            }
        }
    }

    /**
     * 判断字符串是否包含 ASCII 或 Unicode 控制字符。
     *
     * @param value 待检查字符串
     * @return 包含控制字符时返回 true
     */
    private static boolean containsControlCharacter(String value) {
        return value.codePoints().anyMatch(codePoint -> Character.isISOControl(codePoint));
    }

    /**
     * 计算字符串的 UTF-8 字节数。
     *
     * @param value 字符串
     * @return UTF-8 字节数
     */
    private static int utf8Length(String value) {
        return value.getBytes(StandardCharsets.UTF_8).length;
    }

    /**
     * 计算 SHA-256 完整性校验值。
     *
     * @param bytes 输入字节
     * @return 32 字节 SHA-256 值
     */
    private static byte[] sha256(byte[] bytes) {
        try {
            return MessageDigest.getInstance("SHA-256").digest(bytes);
        } catch (NoSuchAlgorithmException exception) {
            throw new IllegalStateException("当前 JVM 不支持 SHA-256", exception);
        }
    }

    /**
     * 比较两个写句柄的全部物理定位字段。
     *
     * @param other 待比较对象
     * @return 字段全部相同时返回 true
     */
    @Override
    public boolean equals(Object other) {
        if (this == other) {
            return true;
        }
        if (!(other instanceof FileStorageWriteHandle handle)) {
            return false;
        }
        return backend.equals(handle.backend) && platform.equals(handle.platform)
                && basePath.equals(handle.basePath) && path.equals(handle.path) && filename.equals(handle.filename);
    }

    /**
     * 计算全部物理定位字段的哈希值。
     *
     * @return 句柄哈希值
     */
    @Override
    public int hashCode() {
        return Objects.hash(backend, platform, basePath, path, filename);
    }

    /**
     * 返回不暴露额外内容的句柄摘要。
     *
     * @return 后端、平台和相对对象路径摘要
     */
    @Override
    public String toString() {
        return "FileStorageWriteHandle{" + "backend='" + backend + '\'' + ", platform='" + platform + '\''
                + ", object='" + path + filename + "'}";
    }
}
