package server.logic.ws_protocol.JSON.messages;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import server.logic.ws_protocol.JSON.ActiveConnectionsRegistry;
import server.logic.ws_protocol.JSON.ConnectionContext;
import server.logic.ws_protocol.JSON.push.WebPushSender;
import server.logic.ws_protocol.JSON.push.WsEventSender;
import shine.db.dao.ActiveSessionsDAO;
import shine.db.dao.SignedMessagesV2DAO;
import shine.db.entities.ActiveSessionEntry;
import shine.db.entities.SignedMessageV2Entry;

import java.util.Base64;
import java.util.List;

public final class SignedMessagesRealtime {
    private static final Logger log = LoggerFactory.getLogger(SignedMessagesRealtime.class);
    private static final ObjectMapper MAPPER = new ObjectMapper();
    private SignedMessagesRealtime() {}

    static DeliveryCounters deliverToTargetSessions(
            SignedMessageV2Entry message,
            String excludeSessionId
    ) throws Exception {
        DeliveryCounters counters = new DeliveryCounters();
        List<ActiveSessionEntry> sessions = ActiveSessionsDAO.getInstance().getByLogin(message.getTargetLogin());
        long now = System.currentTimeMillis();
        for (ActiveSessionEntry s : sessions) {
            String sessionId = s.getSessionId();
            if (excludeSessionId != null && excludeSessionId.equals(sessionId)) {
                continue;
            }
            SignedMessagesV2DAO.getInstance().ensureDeliveryRow(message.getMessageKey(), sessionId, now);
            boolean deliveredOnline = sendEventToSessionIfOnline(sessionId, message, false);
            if (deliveredOnline) {
                counters.wsDelivered++;
                continue;
            }
            if (message.getMessageType() == SignedMessageBlock.TYPE_INCOMING_TEXT) {
                boolean pushed = pushNewMessageNotification(s, message);
                if (pushed) counters.pushDelivered++;
            }
        }
        return counters;
    }

    public static void dispatchPendingForSession(ConnectionContext ctx) {
        if (ctx == null || !ctx.isAuthenticatedUser()) return;
        String login = ctx.getLogin();
        String sessionId = ctx.getSessionId();
        if (isBlank(login) || isBlank(sessionId)) return;

        try {
            List<SignedMessageV2Entry> pending = SignedMessagesV2DAO.getInstance()
                    .listPendingForSession(login, sessionId);
            for (SignedMessageV2Entry e : pending) {
                sendEventToSessionIfOnline(sessionId, e, true);
            }
        } catch (Exception e) {
            log.warn("Failed to dispatch pending messages for sessionId={}", sessionId, e);
        }
    }

    private static boolean sendEventToSessionIfOnline(String sessionId, SignedMessageV2Entry message, boolean backlog) {
        ConnectionContext targetCtx = ActiveConnectionsRegistry.getInstance().getBySessionId(sessionId);
        if (targetCtx == null) return false;

        String blobB64 = Base64.getEncoder().encodeToString(message.getRawBlock());
        ObjectNode payload = MAPPER.createObjectNode();
        payload.put("messageKey", message.getMessageKey());
        payload.put("baseKey", message.getBaseKey());
        payload.put("fromLogin", message.getFromLogin());
        payload.put("toLogin", message.getToLogin());
        payload.put("targetLogin", message.getTargetLogin());
        payload.put("messageType", message.getMessageType());
        payload.put("timeMs", message.getTimeMs());
        payload.put("nonce", message.getNonce());
        payload.put("blobB64", blobB64);
        payload.put("backlog", backlog);
        if (message.getReceiptRefBaseKey() != null) {
            payload.put("receiptRefBaseKey", message.getReceiptRefBaseKey());
        }
        if (message.getReceiptRefType() != null) {
            payload.put("receiptRefType", message.getReceiptRefType());
        }
        return WsEventSender.sendEvent(targetCtx, "SignedMessageArrived", message.getMessageKey(), payload);
    }

    private static boolean pushNewMessageNotification(ActiveSessionEntry session, SignedMessageV2Entry message) {
        try {
            if (session == null) return false;
            if (isBlank(session.getPushEndpoint()) || isBlank(session.getPushP256dhKey()) || isBlank(session.getPushAuthKey())) {
                return false;
            }
            String text = "Вам пришло сообщение от " + message.getFromLogin() + ". Откройте для прочтения.";
            String payload = "{\"kind\":\"new_message\",\"fromLogin\":\"" + jsonEscape(message.getFromLogin()) + "\",\"text\":\"" + jsonEscape(text) + "\"}";
            return WebPushSender.sendBase64Payload(
                    session.getPushEndpoint(),
                    session.getPushP256dhKey(),
                    session.getPushAuthKey(),
                    payload
            );
        } catch (Exception e) {
            return false;
        }
    }

    private static String jsonEscape(String s) {
        if (s == null) return "";
        StringBuilder out = new StringBuilder();
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (c == '\\') out.append("\\\\");
            else if (c == '"') out.append("\\\"");
            else if (c == '\n') out.append("\\n");
            else if (c == '\r') out.append("\\r");
            else if (c == '\t') out.append("\\t");
            else out.append(c);
        }
        return out.toString();
    }

    private static boolean isBlank(String s) {
        return s == null || s.isBlank();
    }

    static final class DeliveryCounters {
        int wsDelivered;
        int pushDelivered;
    }
}
