package tech.easyflow.agent.runtime.media;

import com.easyagents.agent.runtime.media.AgentMediaResolver;
import com.easyagents.agent.runtime.media.AgentMediaResource;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.util.UriUtils;
import tech.easyflow.agent.config.AgentMediaProperties;
import tech.easyflow.common.entity.LoginAccount;

import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.*;

/**
 * Agent 图片上传、归档、鉴权读取和运行时解析服务。
 */
@Service
public class AgentMediaService {

    public static final String MODE_FORMAL = "FORMAL";
    public static final String MODE_DRAFT = "DRAFT";

    private final AgentImageProcessor imageProcessor;
    private final AgentMediaObjectStorage objectStorage;
    private final RedisAgentMediaUploadStore uploadStore;
    private final AgentMediaProperties properties;

    /**
     * 创建 Agent 媒体服务。
     *
     * @param imageProcessor 图片处理器
     * @param objectStorage 私有对象存储
     * @param uploadStore 临时凭据存储
     * @param properties Agent 媒体配置
     */
    public AgentMediaService(AgentImageProcessor imageProcessor,
                             AgentMediaObjectStorage objectStorage,
                             RedisAgentMediaUploadStore uploadStore,
                             AgentMediaProperties properties) {
        this.imageProcessor = imageProcessor;
        this.objectStorage = objectStorage;
        this.uploadStore = uploadStore;
        this.properties = properties;
    }

    /**
     * 上传一张聊天临时图片。
     *
     * @param file 图片文件
     * @param mode 聊天模式
     * @param agentId Agent ID
     * @param sessionId 会话 ID
     * @param account 当前账号
     * @return 上传结果
     */
    public AgentMediaUploadView upload(MultipartFile file,
                                       String mode,
                                       String agentId,
                                       String sessionId,
                                       LoginAccount account) {
        String safeMode = requireMode(mode);
        String safeAgentId = requireText(agentId, "Agent ID 不能为空");
        String safeSessionId = requireText(sessionId, "会话 ID 不能为空");
        Identity identity = identity(account);
        AgentImageData image = imageProcessor.process(file);
        String uploadId = UUID.randomUUID().toString().replace("-", "");
        String objectKey = "temp/%s/%s/%s.%s".formatted(identity.tenantId, identity.userId,
                uploadId, image.extension());
        objectStorage.put(objectKey, image.bytes(), image.mimeType());
        AgentMediaUploadRecord record = new AgentMediaUploadRecord();
        record.setUploadId(uploadId);
        record.setTenantId(identity.tenantId);
        record.setUserId(identity.userId);
        record.setMode(safeMode);
        record.setAgentId(safeAgentId);
        record.setSessionId(safeSessionId);
        record.setObjectKey(objectKey);
        record.setOriginalName(safeOriginalName(file.getOriginalFilename(), image.extension()));
        record.setMimeType(image.mimeType());
        record.setExtension(image.extension());
        record.setSize(image.bytes().length);
        record.setWidth(image.width());
        record.setHeight(image.height());
        record.setSha256(image.sha256());
        record.setCreatedAt(Instant.now());
        try {
            uploadStore.create(record);
        } catch (RuntimeException error) {
            objectStorage.delete(objectKey);
            throw error;
        }
        return toView(record);
    }

    /**
     * 删除当前账号尚未绑定的临时图片。
     *
     * @param uploadId 上传 ID
     * @param account 当前账号
     */
    public void deleteUpload(String uploadId, LoginAccount account) {
        AgentMediaUploadRecord record = uploadStore.find(uploadId).orElse(null);
        if (record == null) {
            return;
        }
        assertOwner(record, identity(account));
        objectStorage.delete(record.getObjectKey());
        uploadStore.delete(record);
    }

    /**
     * 幂等删除指定草稿作用域内的临时图片。
     *
     * @param uploadIds 上传 ID
     * @param mode 聊天模式
     * @param agentId Agent ID
     * @param sessionId 会话 ID
     * @param account 当前账号
     */
    public void deleteUploadsForScope(List<String> uploadIds,
                                      String mode,
                                      String agentId,
                                      String sessionId,
                                      LoginAccount account) {
        Identity identity = identity(account);
        String safeMode = requireMode(mode);
        String safeAgentId = requireText(agentId, "Agent ID 不能为空");
        String safeSessionId = requireText(sessionId, "会话 ID 不能为空");
        for (String uploadId : uploadIds == null ? List.<String>of() : uploadIds.stream()
                .filter(StringUtils::hasText).map(String::trim).distinct().toList()) {
            AgentMediaUploadRecord record = uploadStore.find(uploadId).orElse(null);
            if (record == null) {
                continue;
            }
            assertOwner(record, identity);
            if (!safeMode.equals(record.getMode())
                    || !safeAgentId.equals(record.getAgentId())
                    || !safeSessionId.equals(record.getSessionId())) {
                throw new ResponseStatusException(HttpStatus.FORBIDDEN, "图片不属于当前聊天会话");
            }
            objectStorage.delete(record.getObjectKey());
            uploadStore.delete(record);
        }
    }

    /**
     * 校验并取得本轮图片上传凭据。
     *
     * @param uploadIds 上传 ID
     * @param mode 聊天模式
     * @param agentId Agent ID
     * @param sessionId 会话 ID
     * @param account 当前账号
     * @return 有序上传凭据
     */
    public List<AgentMediaUploadRecord> requireUploads(List<String> uploadIds,
                                                        String mode,
                                                        String agentId,
                                                        String sessionId,
                                                        LoginAccount account) {
        List<String> ids = uploadIds == null ? List.of() : uploadIds.stream()
                .filter(StringUtils::hasText).map(String::trim).distinct().toList();
        if (ids.size() > properties.getMaxImageCount()) {
            throw badRequest("每次最多发送 " + properties.getMaxImageCount() + " 张图片");
        }
        if (ids.isEmpty()) {
            return List.of();
        }
        Identity identity = identity(account);
        String safeMode = requireMode(mode);
        String safeAgentId = requireText(agentId, "Agent ID 不能为空");
        String safeSessionId = requireText(sessionId, "会话 ID 不能为空");
        List<AgentMediaUploadRecord> records = new ArrayList<>(ids.size());
        for (String uploadId : ids) {
            AgentMediaUploadRecord record = uploadStore.find(uploadId)
                    .orElseThrow(() -> badRequest("图片已过期，请重新上传"));
            assertOwner(record, identity);
            if (!safeMode.equals(record.getMode())
                    || !safeAgentId.equals(record.getAgentId())
                    || !safeSessionId.equals(record.getSessionId())) {
                throw new ResponseStatusException(HttpStatus.FORBIDDEN, "图片不属于当前聊天会话");
            }
            records.add(record);
        }
        return records;
    }

    /**
     * 将正式聊天图片从临时区归档到消息目录。
     *
     * @param uploads 临时图片
     * @param sessionId 会话 ID
     * @param messageId 消息 ID
     * @param account 当前账号
     * @return 已绑定图片
     */
    public List<AgentBoundMedia> bindFormal(List<AgentMediaUploadRecord> uploads,
                                            String sessionId,
                                            String messageId,
                                            LoginAccount account) {
        if (uploads == null || uploads.isEmpty()) {
            return List.of();
        }
        Identity identity = identity(account);
        List<AgentBoundMedia> result = new ArrayList<>(uploads.size());
        for (int index = 0; index < uploads.size(); index++) {
            AgentMediaUploadRecord upload = uploads.get(index);
            assertOwner(upload, identity);
            String reference = upload.getFormalReference();
            if (StringUtils.hasText(reference)) {
                FormalReference formal = parseFormalReference(reference);
                if (!digits(sessionId).equals(formal.sessionId)) {
                    throw new ResponseStatusException(HttpStatus.CONFLICT, "图片已绑定到其他聊天会话");
                }
                result.add(new AgentBoundMedia(reference, upload.getMimeType(), displayPayload(upload, reference)));
                continue;
            }
            reference = formalReference(sessionId, messageId, index, upload.getExtension());
            String targetKey = formalObjectKey(identity, sessionId, messageId, index, upload.getExtension());
            objectStorage.copy(upload.getObjectKey(), targetKey);
            uploadStore.markFormalBinding(upload, reference);
            result.add(new AgentBoundMedia(reference, upload.getMimeType(), displayPayload(upload, reference)));
        }
        return result;
    }

    /**
     * 删除当前账号一个正式会话归档的全部图片。
     *
     * @param sessionId 会话 ID
     * @param account 当前账号
     */
    public void deleteFormalSession(String sessionId, LoginAccount account) {
        Identity identity = identity(account);
        objectStorage.deletePrefix("formal/%s/%s/%s/".formatted(
                identity.tenantId, identity.userId, digits(sessionId)));
    }

    /**
     * 为草稿试运行保留临时图片引用并续期。
     *
     * @param uploads 临时图片
     * @return 已绑定图片
     */
    public List<AgentBoundMedia> bindDraft(List<AgentMediaUploadRecord> uploads) {
        if (uploads == null || uploads.isEmpty()) {
            return List.of();
        }
        List<AgentBoundMedia> result = new ArrayList<>(uploads.size());
        for (AgentMediaUploadRecord upload : uploads) {
            uploadStore.renew(upload);
            String reference = "draft:" + upload.getUploadId();
            result.add(new AgentBoundMedia(reference, upload.getMimeType(), displayPayload(upload, reference)));
        }
        return result;
    }

    /**
     * 读取当前用户可访问的图片引用。
     *
     * @param reference 稳定图片引用
     * @param account 当前账号
     * @return 图片资源
     */
    public AgentMediaResource load(String reference, LoginAccount account) {
        Identity identity = identity(account);
        if (reference != null && reference.startsWith("draft:")) {
            String uploadId = reference.substring("draft:".length());
            AgentMediaUploadRecord record = uploadStore.find(uploadId)
                    .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "图片已过期"));
            assertOwner(record, identity);
            return new AgentMediaResource(record.getMimeType(),
                    objectStorage.get(record.getObjectKey(), properties.getMaxImageBytes()));
        }
        FormalReference formal = parseFormalReference(reference);
        String key = formalObjectKey(identity, formal.sessionId, formal.messageId, formal.index, formal.extension);
        return new AgentMediaResource(mimeType(formal.extension),
                objectStorage.get(key, properties.getMaxImageBytes()));
    }

    /**
     * 创建仅能访问当前账号媒体的 easy-agents 解析器。
     *
     * @param account 当前账号
     * @return 运行时媒体解析器
     */
    public AgentMediaResolver runtimeResolver(LoginAccount account) {
        return reference -> {
            AgentMediaResource resource = load(reference, account);
            if (reference != null && reference.startsWith("draft:")) {
                uploadStore.find(reference.substring("draft:".length())).ifPresent(uploadStore::renew);
            }
            return resource;
        };
    }

    /**
     * 清理过期临时对象。
     *
     * @param limit 单次清理上限
     * @return 清理数量
     */
    public int cleanupExpired(int limit) {
        int cleaned = 0;
        for (AgentMediaUploadRecord indexed : uploadStore.expired(limit)) {
            Optional<AgentMediaUploadRecord> current = uploadStore.find(indexed.getUploadId());
            if (current.isPresent() && current.get().getExpiresAt() != null
                    && current.get().getExpiresAt().isAfter(Instant.now())) {
                uploadStore.removeExpiryIndex(indexed);
                continue;
            }
            objectStorage.delete(indexed.getObjectKey());
            current.ifPresent(uploadStore::delete);
            uploadStore.removeExpiryIndex(indexed);
            cleaned++;
        }
        return cleaned;
    }

    private AgentMediaUploadView toView(AgentMediaUploadRecord record) {
        AgentMediaUploadView view = new AgentMediaUploadView();
        view.setUploadId(record.getUploadId());
        view.setName(record.getOriginalName());
        view.setMimeType(record.getMimeType());
        view.setSize(record.getSize());
        view.setWidth(record.getWidth());
        view.setHeight(record.getHeight());
        view.setExpiresAt(record.getExpiresAt());
        view.setPreviewUrl("/api/v1/agent/media/content?reference="
                + UriUtils.encodeQueryParam("draft:" + record.getUploadId(), StandardCharsets.UTF_8));
        return view;
    }

    private Map<String, Object> displayPayload(AgentMediaUploadRecord upload, String reference) {
        Map<String, Object> payload = new LinkedHashMap<>();
        payload.put("imageRef", reference);
        payload.put("name", upload.getOriginalName());
        payload.put("mimeType", upload.getMimeType());
        payload.put("size", upload.getSize());
        payload.put("width", upload.getWidth());
        payload.put("height", upload.getHeight());
        payload.put("previewUrl", "/api/v1/agent/media/content?reference="
                + UriUtils.encodeQueryParam(reference, StandardCharsets.UTF_8));
        return payload;
    }

    private String formalReference(String sessionId, String messageId, int index, String extension) {
        return "formal:" + sessionId + ":" + messageId + ":" + index + ":" + extension;
    }

    private FormalReference parseFormalReference(String reference) {
        if (reference == null || !reference.startsWith("formal:")) {
            throw badRequest("图片引用无效");
        }
        String[] parts = reference.split(":", -1);
        if (parts.length != 5 || !parts[1].matches("\\d+") || !parts[2].matches("\\d+")
                || !parts[3].matches("\\d+") || !Set.of("png", "jpg", "webp").contains(parts[4])) {
            throw badRequest("图片引用无效");
        }
        return new FormalReference(parts[1], parts[2], Integer.parseInt(parts[3]), parts[4]);
    }

    private String formalObjectKey(Identity identity, String sessionId, String messageId, int index, String extension) {
        return "formal/%s/%s/%s/%s/%d.%s".formatted(identity.tenantId, identity.userId,
                digits(sessionId), digits(messageId), index, extension);
    }

    private String digits(String value) {
        if (value == null || !value.matches("\\d+")) {
            throw badRequest("聊天消息标识无效");
        }
        return value;
    }

    private String mimeType(String extension) {
        return switch (extension) {
            case "jpg" -> "image/jpeg";
            case "webp" -> "image/webp";
            default -> "image/png";
        };
    }

    private void assertOwner(AgentMediaUploadRecord record, Identity identity) {
        if (record == null || !identity.tenantId.equals(record.getTenantId())
                || !identity.userId.equals(record.getUserId())) {
            throw new ResponseStatusException(HttpStatus.FORBIDDEN, "无权访问该图片");
        }
    }

    private Identity identity(LoginAccount account) {
        if (account == null || account.getId() == null || account.getTenantId() == null) {
            throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "当前登录状态失效");
        }
        return new Identity(account.getTenantId().toString(), account.getId().toString());
    }

    private String requireMode(String mode) {
        String normalized = mode == null ? "" : mode.trim().toUpperCase(Locale.ROOT);
        if (!MODE_FORMAL.equals(normalized) && !MODE_DRAFT.equals(normalized)) {
            throw badRequest("聊天模式无效");
        }
        return normalized;
    }

    private String requireText(String value, String message) {
        if (!StringUtils.hasText(value) || value.length() > 200) {
            throw badRequest(message);
        }
        return value.trim();
    }

    private String safeOriginalName(String value, String extension) {
        String name = StringUtils.hasText(value) ? value.replaceAll("[\\r\\n\\t]", " ").trim() : "image." + extension;
        return name.length() > 200 ? name.substring(0, 200) : name;
    }

    private ResponseStatusException badRequest(String message) {
        return new ResponseStatusException(HttpStatus.BAD_REQUEST, message);
    }

    private record Identity(String tenantId, String userId) { }
    private record FormalReference(String sessionId, String messageId, int index, String extension) { }
}
