package server.logic.ws_protocol.JSON.handlers.auth;

import org.eclipse.jetty.websocket.api.Session;
import server.logic.ws_protocol.JSON.ActiveConnectionsRegistry;
import server.logic.ws_protocol.JSON.ConnectionContext;
import shine.db.entities.ActiveSessionEntry;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Locale;

final class EspPairingSupport {

    static final int DEFAULT_TTL_SECONDS = 300;
    static final int MIN_TTL_SECONDS = 60;
    static final int MAX_TTL_SECONDS = 1800;
    static final int REQUEST_RATE_LIMIT = 5;
    static final long REQUEST_RATE_WINDOW_MS = 5 * 60_000L;

    static final int STATUS_PAIRING_REQUIRES_AUTH_SESSION = 463;
    static final int STATUS_PAIRING_RATE_LIMIT = 429;

    static final String STATE_CREATED = "created";
    static final String STATE_APPROVED = "approved";
    static final String STATE_REJECTED = "rejected";
    static final String STATE_EXPIRED = "expired";

    private static final SecureRandom RANDOM = new SecureRandom();
    private static final char[] BASE58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray();

    private EspPairingSupport() {}

    static boolean isTrustedUserSession(ConnectionContext ctx) {
        if (ctx == null || !ctx.isAuthenticatedUser()) return false;
        ActiveSessionEntry activeSession = ctx.getActiveSession();
        return activeSession != null && activeSession.getSessionId() != null && !activeSession.getSessionId().isBlank();
    }

    static List<ConnectionContext> findOnlineTrustedConnections(String login) {
        List<ConnectionContext> out = new ArrayList<>();
        for (ConnectionContext candidate : ActiveConnectionsRegistry.getInstance().getByLogin(login)) {
            if (!isTrustedUserSession(candidate)) continue;
            Session ws = candidate.getWsSession();
            if (ws == null || !ws.isOpen()) continue;
            out.add(candidate);
        }
        return out;
    }

    static int normalizeTtlSeconds(Integer raw) {
        if (raw == null) return DEFAULT_TTL_SECONDS;
        int value = raw;
        if (value < MIN_TTL_SECONDS) return MIN_TTL_SECONDS;
        if (value > MAX_TTL_SECONDS) return MAX_TTL_SECONDS;
        return value;
    }

    static int normalizePayloadType(Integer raw) {
        if (raw == null) return 1;
        return raw;
    }

    static boolean isSupportedPayloadType(int payloadType) {
        return payloadType >= 1 && payloadType <= 3;
    }

    static String normalizeOpaqueHash(String raw) {
        if (raw == null) return null;
        String value = raw.trim();
        if (value.isEmpty()) return null;
        if (value.length() > 512) return value.substring(0, 512);
        return value;
    }

    static String normalizeEncryptedPayload(String raw) {
        if (raw == null) return null;
        String value = raw.trim();
        if (value.isEmpty()) return null;
        if (value.length() > 32768) return value.substring(0, 32768);
        return value;
    }

    static String normalizeReason(String raw) {
        if (raw == null) return "";
        String value = raw.trim();
        if (value.length() <= 160) return value;
        return value.substring(0, 160);
    }

    static PairingFingerprint deriveFingerprint(String login,
                                                String requesterSessionKey,
                                                int requesterSessionType,
                                                String clientPlatform,
                                                int payloadType,
                                                long createdAtMs) throws Exception {
        String canonical = (login == null ? "" : login.toLowerCase(Locale.ROOT).trim())
                + "|" + requesterSessionKey
                + "|" + requesterSessionType
                + "|" + (clientPlatform == null ? "" : clientPlatform.trim())
                + "|" + payloadType
                + "|" + createdAtMs;
        byte[] digest = MessageDigest.getInstance("SHA-256").digest(canonical.getBytes(StandardCharsets.UTF_8));
        long code = ((digest[0] & 0xFFL) << 24)
                | ((digest[1] & 0xFFL) << 16)
                | ((digest[2] & 0xFFL) << 8)
                | (digest[3] & 0xFFL);
        long shortCodeNum = code % 10_000_000L;
        String shortCode = String.format(Locale.ROOT, "%07d", shortCodeNum);
        return new PairingFingerprint(shortCode, toBase58(digest));
    }

    static String newPairingId() {
        byte[] random = new byte[32];
        RANDOM.nextBytes(random);
        return Base64.getUrlEncoder().withoutPadding().encodeToString(random);
    }

    static String toBase58(byte[] input) {
        if (input == null || input.length == 0) return "";
        int zeros = 0;
        while (zeros < input.length && input[zeros] == 0) zeros++;
        byte[] copy = input.clone();
        byte[] tmp = new byte[copy.length * 2];
        int j = tmp.length;
        int startAt = zeros;
        while (startAt < copy.length) {
            int mod = divmod58(copy, startAt);
            if (copy[startAt] == 0) startAt++;
            tmp[--j] = (byte) BASE58[mod];
        }
        while (j < tmp.length && tmp[j] == BASE58[0]) j++;
        while (--zeros >= 0) tmp[--j] = (byte) BASE58[0];
        return new String(tmp, j, tmp.length - j, StandardCharsets.US_ASCII);
    }

    private static int divmod58(byte[] number, int startAt) {
        int remainder = 0;
        for (int i = startAt; i < number.length; i++) {
            int digit256 = number[i] & 0xFF;
            int temp = remainder * 256 + digit256;
            number[i] = (byte) (temp / 58);
            remainder = temp % 58;
        }
        return remainder;
    }

    record PairingFingerprint(String shortCode, String fingerprintB58) {}
}
