package server.logic.ws_protocol.JSON.messages;

import shine.db.dao.SignedMessagesV2DAO;
import shine.db.dao.SolanaUsersDAO;
import shine.db.entities.SignedMessageV2Entry;
import shine.db.entities.SolanaUserEntry;
import utils.crypto.Ed25519Util;

import java.util.Base64;

final class SignedMessagesCore {
    private static final int MAX_PAYLOAD_BYTES = 4096;

    private SignedMessagesCore() {}

    static SignedMessageBlock parseFromB64(String blobB64) {
        try {
            byte[] raw = Base64.getDecoder().decode(blobB64.trim());
            return SignedMessageBlock.parse(raw, MAX_PAYLOAD_BYTES);
        } catch (IllegalArgumentException e) {
            throw new IllegalArgumentException("BAD_BLOCK_FORMAT");
        }
    }

    static void verifyUsersAndSignature(SignedMessageBlock block) throws Exception {
        SolanaUserEntry from = SolanaUsersDAO.getInstance().getByLogin(block.fromLogin);
        SolanaUserEntry to = SolanaUsersDAO.getInstance().getByLogin(block.toLogin);
        if (from == null || to == null) {
            throw new IllegalArgumentException("USER_NOT_FOUND");
        }
        byte[] pubKey32 = Ed25519Util.keyFromBase64(from.getDeviceKey());
        if (!Ed25519Util.verify(block.signedBody, block.signature64, pubKey32)) {
            throw new IllegalArgumentException("BAD_SIGNATURE");
        }
    }

    static void validatePair(SignedMessageBlock incoming, SignedMessageBlock outgoing) {
        if (incoming.messageType % 2 == 0) throw new IllegalArgumentException("BAD_PAIR_TYPES");
        if (outgoing.messageType != incoming.messageType + 1) throw new IllegalArgumentException("BAD_PAIR_TYPES");
        if (!incoming.toLogin.equalsIgnoreCase(outgoing.toLogin)) throw new IllegalArgumentException("BAD_PAIR_KEYS");
        if (!incoming.fromLogin.equalsIgnoreCase(outgoing.fromLogin)) throw new IllegalArgumentException("BAD_PAIR_KEYS");
        if (incoming.timeMs != outgoing.timeMs) throw new IllegalArgumentException("BAD_PAIR_KEYS");
        if (incoming.nonce != outgoing.nonce) throw new IllegalArgumentException("BAD_PAIR_KEYS");

        if (incoming.messageType == SignedMessageBlock.TYPE_READ_INCOMING) {
            ReadReceiptPayload inRef = ReadReceiptPayload.parse(incoming.payloadBytes);
            ReadReceiptPayload outRef = ReadReceiptPayload.parse(outgoing.payloadBytes);
            if (!inRef.refToLogin.equalsIgnoreCase(outRef.refToLogin)
                    || !inRef.refFromLogin.equalsIgnoreCase(outRef.refFromLogin)
                    || inRef.refTimeMs != outRef.refTimeMs
                    || inRef.refNonce != outRef.refNonce
                    || inRef.refType != outRef.refType) {
                throw new IllegalArgumentException("BAD_RECEIPT_REF");
            }
        }
    }

    static SignedMessageV2Entry toEntry(SignedMessageBlock block, String sourceApi, String originSessionId) {
        String baseKey = SignedMessageKeys.baseKey(block.toLogin, block.fromLogin, block.timeMs, block.nonce);
        String messageKey = SignedMessageKeys.messageKey(block.toLogin, block.fromLogin, block.timeMs, block.nonce, block.messageType);

        SignedMessageV2Entry entry = new SignedMessageV2Entry();
        entry.setMessageKey(messageKey);
        entry.setBaseKey(baseKey);
        entry.setTargetLogin(block.targetLogin());
        entry.setFromLogin(block.fromLogin);
        entry.setToLogin(block.toLogin);
        entry.setTimeMs(block.timeMs);
        entry.setNonce(block.nonce);
        entry.setMessageType(block.messageType);
        entry.setRawBlock(block.rawPacket);
        entry.setCreatedAtMs(System.currentTimeMillis());
        entry.setSourceApi(sourceApi);
        entry.setOriginSessionId(originSessionId);

        if (block.messageType == SignedMessageBlock.TYPE_READ_INCOMING
                || block.messageType == SignedMessageBlock.TYPE_READ_OUTGOING_COPY) {
            ReadReceiptPayload ref = ReadReceiptPayload.parse(block.payloadBytes);
            entry.setReceiptRefBaseKey(ref.refBaseKey());
            entry.setReceiptRefType(ref.refType);
        }

        return entry;
    }

    static boolean saveIfAbsent(SignedMessageV2Entry entry) throws Exception {
        return SignedMessagesV2DAO.getInstance().insertIfAbsent(entry);
    }
}
