package blockchain.body;

import utils.config.ShineSignatureConstants;

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

/**
 * HeaderBody — type=0, version=1.
 *
 * В новом формате type/subType/version живут в HEADER блока,
 * поэтому bodyBytes для HeaderBody содержат только payload:
 *
 * bodyBytes (BigEndian):
 *   [TAG_LEN] tag ASCII "SHiNE"
 *   [1] loginLength=N (uint8)
 *   [N] login UTF-8
 */
public final class HeaderBody implements BodyRecord {

    public static final short TYPE = 0;
    public static final short VER  = 1;

    public static final int KEY = ((TYPE & 0xFFFF) << 16) | (VER & 0xFFFF);

    /** Для header subType всегда 0 (служебная совместимость). */
    public static final short SUBTYPE_COMPAT = 0;

    /** TAG формата (ASCII). */
    public static final String TAG = ShineSignatureConstants.BLOCKCHAIN_HEADER_TAG;

    private static final byte[] TAG_ASCII = TAG.getBytes(StandardCharsets.US_ASCII);
    private static final int TAG_LEN = TAG_ASCII.length;

    public final short subType; // всегда 0 (из заголовка блока)
    public final short version; // из заголовка блока
    public final String tag;    // "SHiNE"
    public final String login;

    /** Десериализация из payload bodyBytes (без type/subType/version). */
    public HeaderBody(short subType, short version, byte[] bodyBytes) {
        Objects.requireNonNull(bodyBytes, "bodyBytes == null");

        this.subType = subType;
        this.version = version;

        if ((this.subType & 0xFFFF) != (SUBTYPE_COMPAT & 0xFFFF)) {
            throw new IllegalArgumentException("HeaderBody subType must be 0, got=" + (this.subType & 0xFFFF));
        }
        if ((this.version & 0xFFFF) != (VER & 0xFFFF)) {
            throw new IllegalArgumentException("HeaderBody version must be 1, got=" + (this.version & 0xFFFF));
        }

        // минимум: tag[TAG_LEN] + loginLen[1]
        if (bodyBytes.length < TAG_LEN + 1) throw new IllegalArgumentException("HeaderBody too short");

        ByteBuffer bb = ByteBuffer.wrap(bodyBytes).order(ByteOrder.BIG_ENDIAN);

        byte[] tagBytes = new byte[TAG_LEN];
        bb.get(tagBytes);
        String t = new String(tagBytes, StandardCharsets.US_ASCII);
        if (!TAG.equals(t)) throw new IllegalArgumentException("Bad tag: " + t);
        this.tag = t;

        int loginLen = Byte.toUnsignedInt(bb.get());
        if (loginLen <= 0 || bb.remaining() < loginLen)
            throw new IllegalArgumentException("Bad login length");

        byte[] loginBytes = new byte[loginLen];
        bb.get(loginBytes);
        this.login = new String(loginBytes, StandardCharsets.UTF_8);

        if (bb.remaining() != 0) throw new IllegalArgumentException("Unexpected tail bytes, remaining=" + bb.remaining());
    }

    /** Создание “вручную”. */
    public HeaderBody(String login) {
        Objects.requireNonNull(login, "login == null");
        this.subType = SUBTYPE_COMPAT;
        this.version = VER;
        this.tag = TAG;
        this.login = login;
    }

    @Override
    public HeaderBody check() {
        if ((subType & 0xFFFF) != (SUBTYPE_COMPAT & 0xFFFF))
            throw new IllegalArgumentException("HeaderBody subType must be 0");

        if (login == null || login.isBlank())
            throw new IllegalArgumentException("Login is blank");
        if (!login.matches("^[A-Za-z0-9_]+$"))
            throw new IllegalArgumentException("Login must match ^[A-Za-z0-9_]+$");

        return this;
    }

    @Override
    public byte[] toBytes() {
        byte[] loginUtf8 = login.getBytes(StandardCharsets.UTF_8);
        if (loginUtf8.length == 0 || loginUtf8.length > 255)
            throw new IllegalArgumentException("Login utf8 len must be 1..255");

        int cap = TAG_LEN + 1 + loginUtf8.length;

        ByteBuffer bb = ByteBuffer.allocate(cap).order(ByteOrder.BIG_ENDIAN);
        bb.put(TAG_ASCII);
        bb.put((byte) loginUtf8.length);
        bb.put(loginUtf8);

        return bb.array();
    }

    @Override
    public String toString() {
        return """
                HeaderBody {
                  тип записи        : HEADER (type=0, ver=1)  [в заголовке блока]
                  subType           : 0 (compat)
                  тег формата       : "%s"
                  login владельца   : "%s"
                }
                """.formatted(tag, login);
    }
}