package com.gzzm.lobster.run;

import com.gzzm.lobster.common.IdGenerator;
import com.gzzm.lobster.common.JsonUtil;
import com.gzzm.lobster.common.LobsterException;
import com.gzzm.lobster.common.StreamEventType;
import com.gzzm.lobster.runtime.StreamEvent;
import com.gzzm.platform.commons.Tools;
import net.cyan.arachne.annotation.Service;
import net.cyan.nest.annotation.Inject;

import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * Durable stream-event projection for reconnectable runs.
 */
@Service
public class RunStreamEventService {

    @Inject private RunStreamEventDao dao;

    private RunStreamEventDao dao() {
        try {
            RunStreamEventDao d = Tools.getBean(RunStreamEventDao.class);
            if (d != null) return d;
        } catch (Throwable ignore) { /* fallback */ }
        if (dao != null) return dao;
        throw new LobsterException("run_event.persistence_unavailable",
                "Run stream event persistence is unavailable");
    }

    public StreamEvent record(String userId, String threadId, String runId,
                              long seq, StreamEvent event) {
        if (event == null) return null;
        Map<String, Object> payload = new LinkedHashMap<>(event.getPayload());
        payload.put("eventSeq", seq);
        StreamEvent enriched = StreamEvent.of(event.getType(), payload);
        try {
            RunStreamEvent row = new RunStreamEvent();
            row.setEventId(IdGenerator.prefixed("rse_"));
            row.setRunId(runId);
            row.setThreadId(threadId);
            row.setUserId(userId);
            row.setSeq(seq);
            row.setEventType(event.getType().name());
            row.setPayloadJson(JsonUtil.toJson(payload));
            row.setCreateTime(new Date());
            dao().save(row);
        } catch (LobsterException e) {
            throw e;
        } catch (Throwable t) {
            try { Tools.log("[RunStreamEventService] record failed", t); } catch (Throwable ignore) { /* ignore */ }
            throw new LobsterException("run_event.persist_failed",
                    "Failed to persist run stream event", t);
        }
        return enriched;
    }

    public List<StreamEvent> listAfter(String runId, long afterSeq) {
        return listAfterBefore(runId, afterSeq, 0L);
    }

    public List<StreamEvent> listAfterBefore(String runId, long afterSeq, long beforeSeq) {
        List<StreamEvent> out = new ArrayList<>();
        try {
            List<RunStreamEvent> rows = beforeSeq > 0
                    ? dao().listAfterBefore(runId, afterSeq, beforeSeq)
                    : dao().listAfter(runId, afterSeq);
            if (rows == null) return out;
            for (RunStreamEvent row : rows) {
                if (row == null || row.getEventType() == null) continue;
                Map<String, Object> payload = parsePayload(row.getPayloadJson());
                if (!payload.containsKey("eventSeq") && row.getSeq() != null) {
                    payload.put("eventSeq", row.getSeq());
                }
                out.add(StreamEvent.of(StreamEventType.valueOf(row.getEventType()), payload));
            }
        } catch (LobsterException e) {
            throw e;
        } catch (Throwable t) {
            try { Tools.log("[RunStreamEventService] listAfter failed: " + runId, t); } catch (Throwable ignore) { /* ignore */ }
            throw new LobsterException("run_event.replay_failed",
                    "Failed to replay run stream events", t);
        }
        return out;
    }

    public long maxSeq(String runId) {
        try {
            Long v = dao().maxSeq(runId);
            return v == null ? 0L : v;
        } catch (LobsterException e) {
            throw e;
        } catch (Throwable t) {
            throw new LobsterException("run_event.max_seq_failed",
                    "Failed to read run stream event cursor", t);
        }
    }

    private static Map<String, Object> parsePayload(String json) {
        if (json == null || json.isEmpty()) return new LinkedHashMap<>();
        try {
            Map<String, Object> m = JsonUtil.fromJsonToMap(json);
            return m == null ? new LinkedHashMap<String, Object>() : m;
        } catch (Throwable t) {
            Map<String, Object> m = new LinkedHashMap<>();
            m.put("raw", json);
            return m;
        }
    }
}
