package com.folium.server;

import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.time.Instant;
import java.util.Base64;
import java.util.UUID;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

public final class VoiceTickets {
    private VoiceTickets() { }

    public static String issue(UUID authenticatedPlayer, String name, String room, String dimension,
                               boolean listen, boolean speak, byte[] secret) {
        if (secret.length < 32 || authenticatedPlayer == null
                || name == null || !name.matches("[A-Za-z0-9_]{1,16}")
                || room == null || !room.matches("[A-Za-z0-9_.:-]{1,128}")
                || dimension == null || !dimension.matches("[a-z0-9_]+:[a-z0-9_/.-]{1,64}"))
            throw new IllegalArgumentException("Invalid voice ticket parameters");
        String header = encode("{\"alg\":\"HS256\",\"typ\":\"JWT\"}");
        String claims = "{\"sub\":" + quote(authenticatedPlayer.toString())
                + ",\"name\":" + quote(name) + ",\"room\":" + quote(room)
                + ",\"world\":" + quote(dimension) + ",\"listen\":" + listen
                + ",\"speak\":" + speak + ",\"exp\":" + (Instant.now().getEpochSecond() + 120) + "}";
        String data = header + "." + encode(claims);
        try {
            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(new SecretKeySpec(secret, "HmacSHA256"));
            return data + "." + Base64.getUrlEncoder().withoutPadding().encodeToString(
                    mac.doFinal(data.getBytes(StandardCharsets.US_ASCII)));
        } catch (GeneralSecurityException unavailable) {
            throw new IllegalStateException("HMAC-SHA256 is unavailable", unavailable);
        }
    }

    public static byte[] configure(String endpoint, String ticket, boolean listen, boolean speak, String iceJson) {
        var uri = java.net.URI.create(endpoint);
        boolean loopback = "127.0.0.1".equals(uri.getHost()) || "localhost".equalsIgnoreCase(uri.getHost());
        if (!("wss".equalsIgnoreCase(uri.getScheme()) || loopback && "ws".equalsIgnoreCase(uri.getScheme()))
                || uri.getHost() == null || uri.getUserInfo() != null || uri.getFragment() != null
                || endpoint.length() > 2048 || ticket.length() > 2048 || ticket.isBlank())
            throw new IllegalArgumentException("Invalid voice endpoint or ticket");
        String ice = iceJson == null ? "[]" : iceJson.strip();
        if (!ice.startsWith("[") || !ice.endsWith("]") || ice.indexOf('|') >= 0
                || ice.indexOf('\n') >= 0 || ice.indexOf('\r') >= 0 || ice.length() > 4096)
            throw new IllegalArgumentException("ICE configuration must be a bounded single-line JSON array");
        String message = "voice|configure|{\"endpoint\":" + quote(endpoint) + ",\"ticket\":" + quote(ticket)
                + ",\"listen\":" + listen + ",\"speak\":" + speak + ",\"iceServers\":" + ice + "}";
        byte[] bytes = message.getBytes(StandardCharsets.UTF_8);
        if (bytes.length > 8192) throw new IllegalArgumentException("Voice configuration is too large");
        return bytes;
    }

    private static String encode(String json) {
        return Base64.getUrlEncoder().withoutPadding().encodeToString(json.getBytes(StandardCharsets.UTF_8));
    }

    private static String quote(String value) {
        StringBuilder out = new StringBuilder("\"");
        for (char c : value.toCharArray()) {
            if (c == '"' || c == '\\') out.append('\\').append(c);
            else if (c < 32 || c == '|') out.append(String.format("\\u%04x", (int) c));
            else out.append(c);
        }
        return out.append('"').toString();
    }
}
