package tech.easyflow.agent.runtime.composer;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.mybatisflex.core.keygen.impl.SnowFlakeIDKeyGenerator;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ResponseStatusException;
import tech.easyflow.agent.config.AgentMediaProperties;
import tech.easyflow.agent.runtime.media.AgentMediaService;
import tech.easyflow.agent.runtime.media.AgentMediaUploadRecord;
import tech.easyflow.agent.runtime.media.AgentMediaUploadView;
import tech.easyflow.common.entity.LoginAccount;

import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Optional;

/**
 * 基于 Redis 的 Agent 输入草稿与当前未发送会话管理服务。
 */
@Service
public class AgentComposerDraftService {

    private static final String DRAFT_PREFIX = "easyflow:agent:composer:draft:";
    private static final String REVISION_PREFIX = "easyflow:agent:composer:revision:";
    private static final String ACTIVE_PREFIX = "easyflow:agent:composer:active:";
    private static final DefaultRedisScript<Long> SAVE_SCRIPT = new DefaultRedisScript<>("""
            local current = redis.call('GET', KEYS[2])
            if current and tonumber(current) ~= tonumber(ARGV[1]) then
              return -1
            end
            redis.call('SET', KEYS[1], ARGV[2], 'EX', ARGV[4])
            redis.call('SET', KEYS[2], ARGV[3], 'EX', ARGV[4])
            redis.call('SET', KEYS[3], ARGV[5], 'EX', ARGV[4])
            return tonumber(ARGV[3])
            """, Long.class);

    private final StringRedisTemplate redisTemplate;
    private final ObjectMapper objectMapper;
    private final AgentMediaProperties properties;
    private final AgentMediaService mediaService;

    /**
     * 创建输入草稿服务。
     *
     * @param redisTemplate Redis 模板
     * @param objectMapper JSON 映射器
     * @param properties Agent 媒体配置
     * @param mediaService Agent 媒体服务
     */
    public AgentComposerDraftService(StringRedisTemplate redisTemplate,
                                     ObjectMapper objectMapper,
                                     AgentMediaProperties properties,
                                     AgentMediaService mediaService) {
        this.redisTemplate = redisTemplate;
        this.objectMapper = objectMapper;
        this.properties = properties;
        this.mediaService = mediaService;
    }

    /**
     * 为正式或草稿聊天预分配稳定会话 ID。
     *
     * @param mode 聊天模式
     * @return 会话信息
     */
    public AgentComposerSession allocateSession(String mode) {
        String safeMode = mode(mode);
        long id = new SnowFlakeIDKeyGenerator().nextId();
        return new AgentComposerSession(safeMode,
                AgentMediaService.MODE_DRAFT.equals(safeMode) ? "agent-draft-" + id : String.valueOf(id));
    }

    /**
     * 保存输入草稿并对相关图片续期。
     *
     * @param draft 草稿内容
     * @param account 当前账号
     * @return 保存后的草稿
     */
    public AgentComposerDraft save(AgentComposerDraft draft, LoginAccount account) {
        if (draft == null) {
            throw badRequest("聊天草稿不能为空");
        }
        Identity identity = identity(account);
        String safeMode = mode(draft.getMode());
        String agentId = text(draft.getAgentId(), "Agent ID 不能为空");
        String sessionId = text(draft.getSessionId(), "会话 ID 不能为空");
        validateSessionId(safeMode, sessionId);
        List<AgentMediaUploadRecord> uploads = mediaService.requireUploads(draft.getImageUploadIds(), safeMode,
                agentId, sessionId, account);
        for (AgentMediaUploadRecord upload : uploads) {
            mediaService.bindDraft(List.of(upload));
        }
        draft.setMode(safeMode);
        draft.setAgentId(agentId);
        draft.setSessionId(sessionId);
        draft.setText(draft.getText() == null ? "" : draft.getText());
        if (draft.getText().length() > 100_000) {
            throw badRequest("输入内容过长");
        }
        draft.setImages(uploads.stream().map(this::toView).toList());
        long expectedRevision = Math.max(0L, draft.getRevision());
        long nextRevision = expectedRevision + 1L;
        draft.setRevision(nextRevision);
        Duration ttl = draftTtl();
        draft.setExpiresAt(Instant.now().plus(ttl));
        String scope = scope(identity, safeMode, agentId, sessionId);
        try {
            Long result = redisTemplate.execute(SAVE_SCRIPT,
                    List.of(DRAFT_PREFIX + scope, REVISION_PREFIX + scope, activeKey(identity, safeMode, agentId)),
                    String.valueOf(expectedRevision), objectMapper.writeValueAsString(draft),
                    String.valueOf(nextRevision), String.valueOf(Math.max(1L, ttl.toSeconds())), sessionId);
            if (result == null || result < 0L) {
                throw new ResponseStatusException(HttpStatus.CONFLICT, "聊天草稿已在其他窗口更新，请刷新后重试");
            }
            return draft;
        } catch (ResponseStatusException error) {
            throw error;
        } catch (Exception error) {
            throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "聊天草稿保存失败", error);
        }
    }

    /**
     * 获取指定会话或最近未发送会话的草稿，读取不延长 TTL。
     *
     * @param mode 聊天模式
     * @param agentId Agent ID
     * @param sessionId 会话 ID，可为空
     * @param account 当前账号
     * @return 草稿
     */
    public Optional<AgentComposerDraft> get(String mode,
                                            String agentId,
                                            String sessionId,
                                            LoginAccount account) {
        Identity identity = identity(account);
        String safeMode = mode(mode);
        String safeAgentId = text(agentId, "Agent ID 不能为空");
        String resolvedSessionId = sessionId;
        if (!StringUtils.hasText(resolvedSessionId)) {
            resolvedSessionId = redisTemplate.opsForValue().get(activeKey(identity, safeMode, safeAgentId));
        }
        if (!StringUtils.hasText(resolvedSessionId)) {
            return Optional.empty();
        }
        validateSessionId(safeMode, resolvedSessionId);
        String value = redisTemplate.opsForValue().get(DRAFT_PREFIX
                + scope(identity, safeMode, safeAgentId, resolvedSessionId));
        if (!StringUtils.hasText(value)) {
            return Optional.empty();
        }
        try {
            AgentComposerDraft draft = objectMapper.readValue(value, AgentComposerDraft.class);
            List<AgentMediaUploadRecord> uploads = availableUploads(draft.getImageUploadIds(), safeMode,
                    safeAgentId, resolvedSessionId, account);
            draft.setImageUploadIds(uploads.stream().map(AgentMediaUploadRecord::getUploadId).toList());
            draft.setImages(uploads.stream().map(this::toView).toList());
            return Optional.of(draft);
        } catch (Exception error) {
            if (error instanceof ResponseStatusException responseError) {
                throw responseError;
            }
            throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "聊天草稿读取失败", error);
        }
    }

    /**
     * 读取仍有效的草稿图片，跳过自然过期项并保留权限或归属异常。
     *
     * @param uploadIds 图片上传 ID
     * @param mode 聊天模式
     * @param agentId Agent ID
     * @param sessionId 会话 ID
     * @param account 当前账号
     * @return 仍有效且保持原顺序的图片
     */
    private List<AgentMediaUploadRecord> availableUploads(List<String> uploadIds,
                                                           String mode,
                                                           String agentId,
                                                           String sessionId,
                                                           LoginAccount account) {
        List<AgentMediaUploadRecord> available = new ArrayList<>();
        for (String uploadId : uploadIds == null ? List.<String>of() : uploadIds) {
            try {
                available.addAll(mediaService.requireUploads(List.of(uploadId), mode, agentId, sessionId, account));
            } catch (ResponseStatusException error) {
                if (error.getStatusCode() == HttpStatus.BAD_REQUEST
                        && "图片已过期，请重新上传".equals(error.getReason())) {
                    continue;
                }
                throw error;
            }
        }
        return available;
    }

    /**
     * 删除已发送或主动清空的输入草稿。
     *
     * @param mode 聊天模式
     * @param agentId Agent ID
     * @param sessionId 会话 ID
     * @param account 当前账号
     */
    public void delete(String mode, String agentId, String sessionId, LoginAccount account) {
        delete(mode, agentId, sessionId, List.of(), true, account);
    }

    /**
     * 删除输入草稿，并按调用语义选择是否清理临时图片。
     *
     * @param mode 聊天模式
     * @param agentId Agent ID
     * @param sessionId 会话 ID
     * @param requestedUploadIds 调用方当前持有的上传 ID
     * @param deleteUploads 是否删除临时图片
     * @param account 当前账号
     */
    public void delete(String mode,
                       String agentId,
                       String sessionId,
                       List<String> requestedUploadIds,
                       boolean deleteUploads,
                       LoginAccount account) {
        Identity identity = identity(account);
        String safeMode = mode(mode);
        String safeAgentId = text(agentId, "Agent ID 不能为空");
        String safeSessionId = text(sessionId, "会话 ID 不能为空");
        validateSessionId(safeMode, safeSessionId);
        String scope = scope(identity, safeMode, safeAgentId, safeSessionId);
        String draftKey = DRAFT_PREFIX + scope;
        if (deleteUploads) {
            LinkedHashSet<String> uploadIds = new LinkedHashSet<>();
            String serializedDraft = redisTemplate.opsForValue().get(draftKey);
            if (StringUtils.hasText(serializedDraft)) {
                try {
                    AgentComposerDraft storedDraft = objectMapper.readValue(serializedDraft, AgentComposerDraft.class);
                    if (storedDraft.getImageUploadIds() != null) {
                        uploadIds.addAll(storedDraft.getImageUploadIds());
                    }
                } catch (Exception error) {
                    throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "聊天草稿删除失败", error);
                }
            }
            if (requestedUploadIds != null) {
                uploadIds.addAll(requestedUploadIds);
            }
            mediaService.deleteUploadsForScope(new ArrayList<>(uploadIds), safeMode,
                    safeAgentId, safeSessionId, account);
        }
        redisTemplate.delete(List.of(draftKey, REVISION_PREFIX + scope));
        String activeKey = activeKey(identity, safeMode, safeAgentId);
        String activeSession = redisTemplate.opsForValue().get(activeKey);
        if (safeSessionId.equals(activeSession)) {
            redisTemplate.delete(activeKey);
        }
    }

    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=draft%3A" + record.getUploadId());
        return view;
    }

    private void validateSessionId(String mode, String sessionId) {
        boolean valid = AgentMediaService.MODE_DRAFT.equals(mode)
                ? sessionId.matches("agent-draft-\\d+")
                : sessionId.matches("\\d+");
        if (!valid) {
            throw badRequest("会话 ID 无效");
        }
    }

    private String scope(Identity identity, String mode, String agentId, String sessionId) {
        return identity.tenantId + ":" + identity.userId + ":" + mode + ":" + agentId + ":" + sessionId;
    }

    private String activeKey(Identity identity, String mode, String agentId) {
        return ACTIVE_PREFIX + identity.tenantId + ":" + identity.userId + ":" + mode + ":" + agentId;
    }

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

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

    private Duration draftTtl() {
        Duration ttl = properties.getComposerDraftTtl();
        return ttl == null || ttl.isZero() || ttl.isNegative() ? Duration.ofHours(24) : ttl;
    }

    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 ResponseStatusException badRequest(String message) {
        return new ResponseStatusException(HttpStatus.BAD_REQUEST, message);
    }

    private record Identity(String tenantId, String userId) { }
}
