package com.easyagents.skill.store;

import com.easyagents.skill.exception.SkillException;
import com.easyagents.skill.util.SkillHashes;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.security.DigestOutputStream;
import java.security.MessageDigest;

/**
 * Skill 二进制内容流式存储与引用生命周期接口。
 */
public interface SkillContentStore {

    /**
     * 保存内容并返回内容引用。
     *
     * @param bytes 内容字节
     * @return 内容引用
     */
    String put(byte[] bytes);

    /**
     * 流式保存内容并返回内容引用。
     *
     * <p>兼容默认实现仅缓存单个文件；正式持久化实现应覆盖该方法以直接流式写入。</p>
     *
     * @param inputStream 内容流，不由本方法关闭
     * @param maxBytes 最大允许字节数
     * @return 内容引用
     */
    default String put(InputStream inputStream, long maxBytes) {
        return put(readBounded(inputStream, maxBytes));
    }

    /**
     * 暂存内容，供完成全包校验后统一提交。
     *
     * <p>为兼容旧实现，默认实现会立即写入；正式实现应覆盖并提供真实暂存区。</p>
     *
     * @param inputStream 内容流，不由本方法关闭
     * @param maxBytes 最大允许字节数
     * @return 暂存结果
     */
    default SkillContentStage stage(InputStream inputStream, long maxBytes) {
        if (inputStream == null || maxBytes < 0) {
            throw new SkillException("Valid Skill content stream and limit are required.");
        }
        Path temporaryFile = null;
        try {
            temporaryFile = Files.createTempFile("easy-agents-skill-content-", ".stage");
            MessageDigest digest = SkillHashes.newSha256Digest();
            long total = 0;
            try (DigestOutputStream output = new DigestOutputStream(
                    Files.newOutputStream(temporaryFile, StandardOpenOption.TRUNCATE_EXISTING), digest)) {
                byte[] buffer = new byte[16 * 1024];
                int read;
                while ((read = inputStream.read(buffer)) >= 0) {
                    if (read == 0) {
                        continue;
                    }
                    if (total > maxBytes - read) {
                        throw new SkillException("Skill content exceeds " + maxBytes + " bytes.");
                    }
                    output.write(buffer, 0, read);
                    total += read;
                }
            }
            return SkillContentStage.compatibility(
                    temporaryFile, SkillHashes.toHex(digest.digest()), total);
        } catch (IOException | RuntimeException e) {
            deleteTemporaryFile(temporaryFile);
            if (e instanceof SkillException skillException) {
                throw skillException;
            }
            throw new SkillException("Failed to stage Skill content stream.", e);
        }
    }

    /**
     * 提交暂存内容。
     *
     * @param stage 暂存结果
     * @return 最终内容引用
     */
    default String commit(SkillContentStage stage) {
        if (stage == null) {
            throw new SkillException("Skill content stage is required.");
        }
        Path compatibilityPath = stage.compatibilityPath();
        if (compatibilityPath != null) {
            try (InputStream input = Files.newInputStream(compatibilityPath)) {
                String contentRef = put(input, stage.getSize());
                if (!stage.getContentRef().equals(contentRef)) {
                    release(contentRef);
                    throw new SkillException("Skill content store returned a non-hash content reference.");
                }
                return contentRef;
            } catch (IOException e) {
                throw new SkillException("Failed to commit staged Skill content.", e);
            } finally {
                deleteTemporaryFile(compatibilityPath);
            }
        }
        return stage.getContentRef();
    }

    /**
     * 回滚尚未提交的内容。
     *
     * @param stage 暂存结果
     */
    default void rollback(SkillContentStage stage) {
        if (stage != null) {
            deleteTemporaryFile(stage.compatibilityPath());
            if (stage.isAlreadyCommitted()) {
                release(stage.getContentRef());
            }
        }
    }

    /**
     * 增加正式内容引用计数。
     *
     * @param contentRef 内容引用
     */
    default void retain(String contentRef) {
        // 旧实现没有引用计数，保留兼容空操作。
    }

    /**
     * 释放正式内容引用；引用归零后实现可以删除物理内容。
     *
     * @param contentRef 内容引用
     */
    default void release(String contentRef) {
        // 旧实现没有引用计数，保留兼容空操作。
    }

    /**
     * 打开内容流。
     *
     * @param contentRef 内容引用
     * @return 内容流
     */
    InputStream open(String contentRef);

    /**
     * 读取全部内容。
     *
     * @param contentRef 内容引用
     * @return 内容字节
     */
    byte[] readAllBytes(String contentRef);

    /**
     * 判断内容是否存在。
     *
     * @param contentRef 内容引用
     * @return 存在时为 true
     */
    boolean exists(String contentRef);

    private static byte[] readBounded(InputStream inputStream, long maxBytes) {
        if (inputStream == null) {
            throw new SkillException("Skill content input stream is required.");
        }
        if (maxBytes < 0) {
            throw new SkillException("Skill content max bytes cannot be negative.");
        }
        try {
            ByteArrayOutputStream output = new ByteArrayOutputStream((int) Math.min(maxBytes, 8_192));
            byte[] buffer = new byte[8_192];
            long total = 0;
            int read;
            while ((read = inputStream.read(buffer)) >= 0) {
                if (read == 0) {
                    continue;
                }
                total += read;
                if (total > maxBytes) {
                    throw new SkillException("Skill content exceeds " + maxBytes + " bytes.");
                }
                output.write(buffer, 0, read);
            }
            return output.toByteArray();
        } catch (IOException e) {
            throw new SkillException("Failed to read Skill content stream.", e);
        }
    }

    private static void deleteTemporaryFile(Path path) {
        if (path == null) {
            return;
        }
        try {
            Files.deleteIfExists(path);
        } catch (IOException ignored) {
            // 临时文件清理由操作系统兜底，调用方异常语义保持不变。
        }
    }
}
