package server.logic.ws_protocol.JSON.messages;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;

final class SignedMessageBlock {
    static final byte[] PREFIX = "SHiNE_dm2".getBytes(StandardCharsets.US_ASCII);
    static final int TYPE_INCOMING_TEXT = 1;
    static final int TYPE_OUTGOING_COPY = 2;
    static final int TYPE_READ_INCOMING = 3;
    static final int TYPE_READ_OUTGOING_COPY = 4;

    final String toLogin;
    final String fromLogin;
    final long timeMs;
    final long nonce;
    final int messageType;
    final byte[] payloadBytes;
    final byte[] signedBody;
    final byte[] signature64;
    final byte[] rawPacket;

    private SignedMessageBlock(
            String toLogin,
            String fromLogin,
            long timeMs,
            long nonce,
            int messageType,
            byte[] payloadBytes,
            byte[] signedBody,
            byte[] signature64,
            byte[] rawPacket
    ) {
        this.toLogin = toLogin;
        this.fromLogin = fromLogin;
        this.timeMs = timeMs;
        this.nonce = nonce;
        this.messageType = messageType;
        this.payloadBytes = payloadBytes;
        this.signedBody = signedBody;
        this.signature64 = signature64;
        this.rawPacket = rawPacket;
    }

    static SignedMessageBlock parse(byte[] raw, int maxPayloadBytes) {
        if (raw == null || raw.length < PREFIX.length + 1 + 1 + 8 + 4 + 2 + 2 + 64) {
            throw new IllegalArgumentException("BAD_LEN");
        }
        if (raw.length > 8192) {
            throw new IllegalArgumentException("PAYLOAD_TOO_LARGE");
        }

        ByteBuffer bb = ByteBuffer.wrap(raw).order(ByteOrder.BIG_ENDIAN);
        byte[] prefix = new byte[PREFIX.length];
        bb.get(prefix);
        if (!Arrays.equals(prefix, PREFIX)) {
            throw new IllegalArgumentException("BAD_PREFIX");
        }

        String toLogin = readAscii(bb, 1, 60, "BAD_TO_LOGIN");
        String fromLogin = readAscii(bb, 1, 60, "BAD_FROM_LOGIN");

        long timeMs = bb.getLong();
        if (timeMs < 0) throw new IllegalArgumentException("BAD_TIME");

        long nonce = Integer.toUnsignedLong(bb.getInt());
        int messageType = Short.toUnsignedInt(bb.getShort());
        if (messageType < TYPE_INCOMING_TEXT || messageType > TYPE_READ_OUTGOING_COPY) {
            throw new IllegalArgumentException("BAD_MESSAGE_TYPE");
        }

        int payloadLen = Short.toUnsignedInt(bb.getShort());
        if (payloadLen < 1 || payloadLen > maxPayloadBytes) {
            throw new IllegalArgumentException("BAD_MESSAGE_LEN");
        }
        if (bb.remaining() != payloadLen + 64) {
            throw new IllegalArgumentException("BAD_LEN");
        }

        byte[] payload = new byte[payloadLen];
        bb.get(payload);
        byte[] signature64 = new byte[64];
        bb.get(signature64);
        byte[] signedBody = Arrays.copyOf(raw, raw.length - 64);

        return new SignedMessageBlock(
                toLogin, fromLogin, timeMs, nonce, messageType, payload, signedBody, signature64, raw
        );
    }

    boolean isIncomingType() {
        return messageType == TYPE_INCOMING_TEXT || messageType == TYPE_READ_INCOMING;
    }

    boolean isOutgoingCopyType() {
        return messageType == TYPE_OUTGOING_COPY || messageType == TYPE_READ_OUTGOING_COPY;
    }

    String targetLogin() {
        return isIncomingType() ? toLogin : fromLogin;
    }

    private static String readAscii(ByteBuffer bb, int minLen, int maxLen, String code) {
        if (!bb.hasRemaining()) throw new IllegalArgumentException(code);
        int len = Byte.toUnsignedInt(bb.get());
        if (len < minLen || len > maxLen || bb.remaining() < len) {
            throw new IllegalArgumentException(code);
        }
        byte[] bytes = new byte[len];
        bb.get(bytes);
        for (byte b : bytes) {
            if (b < 0x20 || b > 0x7E) throw new IllegalArgumentException(code);
        }
        return new String(bytes, StandardCharsets.US_ASCII);
    }
}
