package tech.easyflow.agent.runtime.media;

import io.minio.*;
import io.minio.errors.ErrorResponseException;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import org.dromara.x.file.storage.core.FileStorageService;
import org.dromara.x.file.storage.core.platform.MinioFileStorage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.event.EventListener;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.web.server.ResponseStatusException;
import tech.easyflow.agent.config.AgentMediaProperties;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * 基于独立私有 MinIO 桶的 Agent 媒体对象存储。
 */
@Service
public class AgentMediaObjectStorage {

    private static final Logger LOG = LoggerFactory.getLogger(AgentMediaObjectStorage.class);
    private final FileStorageService fileStorageService;
    private final AgentMediaProperties properties;
    private final AtomicBoolean bucketReady = new AtomicBoolean(false);

    /**
     * 创建 Agent 媒体对象存储。
     *
     * @param fileStorageService x-file-storage 服务
     * @param properties Agent 媒体配置
     */
    public AgentMediaObjectStorage(FileStorageService fileStorageService, AgentMediaProperties properties) {
        this.fileStorageService = fileStorageService;
        this.properties = properties;
    }

    /**
     * 应用启动后预创建私有桶；失败时保留完整日志，首次使用会再次尝试。
     */
    @EventListener(ApplicationReadyEvent.class)
    public void initializeBucket() {
        try {
            ensureBucket();
        } catch (Exception error) {
            LOG.error("Agent media bucket initialization failed", error);
        }
    }

    /**
     * 写入对象。
     *
     * @param objectKey 对象键
     * @param data 对象字节
     * @param mimeType MIME 类型
     */
    public void put(String objectKey, byte[] data, String mimeType) {
        try {
            MinioFileStorage storage = storage();
            ensureBucket();
            storage.getClient().putObject(PutObjectArgs.builder()
                    .bucket(storage.getBucketName())
                    .object(fullKey(storage, objectKey))
                    .stream(new java.io.ByteArrayInputStream(data), data.length, -1)
                    .contentType(mimeType)
                    .build());
        } catch (Exception error) {
            throw storageError("图片上传到对象存储失败", error);
        }
    }

    /**
     * 读取对象并限制最大内存占用。
     *
     * @param objectKey 对象键
     * @param maxBytes 最大读取字节数
     * @return 对象字节
     */
    public byte[] get(String objectKey, long maxBytes) {
        try {
            MinioFileStorage storage = storage();
            ensureBucket();
            try (InputStream input = storage.getClient().getObject(GetObjectArgs.builder()
                    .bucket(storage.getBucketName()).object(fullKey(storage, objectKey)).build());
                 ByteArrayOutputStream output = new ByteArrayOutputStream()) {
                byte[] buffer = new byte[8192];
                long total = 0L;
                int read;
                while ((read = input.read(buffer)) >= 0) {
                    total += read;
                    if (total > maxBytes) {
                        throw new ResponseStatusException(HttpStatus.PAYLOAD_TOO_LARGE, "图片对象超过允许大小");
                    }
                    output.write(buffer, 0, read);
                }
                return output.toByteArray();
            }
        } catch (ResponseStatusException error) {
            throw error;
        } catch (ErrorResponseException error) {
            String code = error.errorResponse() == null ? null : error.errorResponse().code();
            if ("NoSuchKey".equals(code) || "NoSuchObject".equals(code)) {
                throw new ResponseStatusException(HttpStatus.NOT_FOUND, "图片不存在或已删除", error);
            }
            throw storageError("图片对象读取失败", error);
        } catch (Exception error) {
            throw storageError("图片对象读取失败", error);
        }
    }

    /**
     * 在私有桶内复制对象。
     *
     * @param sourceKey 源对象键
     * @param targetKey 目标对象键
     */
    public void copy(String sourceKey, String targetKey) {
        try {
            MinioFileStorage storage = storage();
            ensureBucket();
            storage.getClient().copyObject(CopyObjectArgs.builder()
                    .bucket(storage.getBucketName())
                    .object(fullKey(storage, targetKey))
                    .source(CopySource.builder().bucket(storage.getBucketName())
                            .object(fullKey(storage, sourceKey)).build())
                    .build());
        } catch (Exception error) {
            throw storageError("图片对象归档失败", error);
        }
    }

    /**
     * 删除对象，重复删除视为成功。
     *
     * @param objectKey 对象键
     */
    public void delete(String objectKey) {
        try {
            MinioFileStorage storage = storage();
            ensureBucket();
            storage.getClient().removeObject(RemoveObjectArgs.builder()
                    .bucket(storage.getBucketName()).object(fullKey(storage, objectKey)).build());
        } catch (Exception error) {
            throw storageError("图片对象删除失败", error);
        }
    }

    /**
     * 删除指定前缀下的全部对象。
     *
     * @param objectPrefix 对象键前缀
     */
    public void deletePrefix(String objectPrefix) {
        try {
            MinioFileStorage storage = storage();
            ensureBucket();
            String fullPrefix = fullKey(storage, objectPrefix);
            List<DeleteObject> batch = new ArrayList<>(1000);
            for (Result<Item> result : storage.getClient().listObjects(ListObjectsArgs.builder()
                    .bucket(storage.getBucketName())
                    .prefix(fullPrefix)
                    .recursive(true)
                    .build())) {
                batch.add(new DeleteObject(result.get().objectName()));
                if (batch.size() == 1000) {
                    removeBatch(storage, batch);
                    batch.clear();
                }
            }
            removeBatch(storage, batch);
        } catch (Exception error) {
            throw storageError("图片对象目录删除失败", error);
        }
    }

    private synchronized void ensureBucket() throws Exception {
        if (bucketReady.get()) {
            return;
        }
        MinioFileStorage storage = storage();
        boolean exists = storage.getClient().bucketExists(
                BucketExistsArgs.builder().bucket(storage.getBucketName()).build());
        if (!exists) {
            storage.getClient().makeBucket(MakeBucketArgs.builder().bucket(storage.getBucketName()).build());
        }
        bucketReady.set(true);
    }

    private MinioFileStorage storage() {
        MinioFileStorage storage = fileStorageService.getFileStorage(properties.getPlatform());
        if (storage == null) {
            throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR,
                    "Agent 私有媒体存储平台未配置");
        }
        return storage;
    }

    private void removeBatch(MinioFileStorage storage, List<DeleteObject> objects) throws Exception {
        if (objects.isEmpty()) {
            return;
        }
        Iterable<Result<DeleteError>> errors = storage.getClient().removeObjects(RemoveObjectsArgs.builder()
                .bucket(storage.getBucketName())
                .objects(List.copyOf(objects))
                .build());
        for (Result<DeleteError> result : errors) {
            DeleteError error = result.get();
            throw new IllegalStateException("删除 MinIO 对象失败: " + error.objectName() + ", " + error.message());
        }
    }

    private String fullKey(MinioFileStorage storage, String objectKey) {
        String basePath = storage.getBasePath();
        if (basePath == null || basePath.isBlank()) {
            return objectKey;
        }
        return basePath.replaceAll("/+$", "") + "/" + objectKey.replaceAll("^/+", "");
    }

    private ResponseStatusException storageError(String message, Exception error) {
        LOG.error(message, error);
        return new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, message, error);
    }
}
