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

import server.logic.ws_protocol.JSON.ConnectionContext;
import server.logic.ws_protocol.JSON.entyties.Net_Request;
import server.logic.ws_protocol.JSON.entyties.Net_Response;
import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler;
import server.logic.ws_protocol.JSON.handlers.system.entyties.Net_GetCallIceConfig_Request;
import server.logic.ws_protocol.JSON.handlers.system.entyties.Net_GetCallIceConfig_Response;
import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory;
import server.logic.ws_protocol.WireCodes;
import utils.config.AppConfig;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;

/**
 * Выдаёт ICE-конфиг для звонка.
 * Поддерживает два режима TURN:
 * 1) Временные учётки через shared-secret (рекомендуется).
 * 2) Статический username/password (fallback).
 */
public class Net_GetCallIceConfig_Handler implements JsonMessageHandler {
    private static final AppConfig CONFIG = AppConfig.getInstance();
    private static final String HMAC_SHA1 = "HmacSHA1";
    private static final int DEFAULT_TTL_SEC = 600;
    private static final int MIN_TTL_SEC = 60;
    private static final int MAX_TTL_SEC = 3600;

    @Override
    public Net_Response handle(Net_Request baseRequest, ConnectionContext ctx) {
        Net_GetCallIceConfig_Request req = (Net_GetCallIceConfig_Request) baseRequest;
        if (ctx == null || !ctx.isAuthenticatedUser()) {
            return NetExceptionResponseFactory.error(req, WireCodes.Status.UNVERIFIED, "NOT_AUTHENTICATED", "Требуется авторизация");
        }

        int ttlSec = clampTtlSec(readInt("call.ice.turn.ttlSec", DEFAULT_TTL_SEC));
        long nowMs = System.currentTimeMillis();
        long expiresAtMs = nowMs + ttlSec * 1000L;

        List<String> stunUrls = parseUrls(
                readStr("call.ice.stun.urls", "stun:stun.l.google.com:19302")
        );
        List<String> turnUrls = parseUrls(readStr("call.ice.turn.urls", ""));

        String turnUsername = "";
        String turnPassword = "";
        List<Net_GetCallIceConfig_Response.TurnServerConfig> turnServers = buildTurnServers(ctx, nowMs, ttlSec);

        String sharedSecret = readStr("call.ice.turn.sharedSecret", "");
        String staticUsername = readStr("call.ice.turn.username", "");
        String staticPassword = readStr("call.ice.turn.password", "");

        if (!turnServers.isEmpty()) {
            Net_GetCallIceConfig_Response.TurnServerConfig primary = turnServers.get(0);
            turnUrls = primary.getUrls();
            turnUsername = primary.getUsername();
            turnPassword = primary.getPassword();
        } else if (!turnUrls.isEmpty()) {
            if (!sharedSecret.isBlank()) {
                long expiresEpochSec = nowMs / 1000L + ttlSec;
                expiresAtMs = expiresEpochSec * 1000L;
                String prefix = readStr("call.ice.turn.userPrefix", "shine");
                String safeLogin = sanitizeLogin(ctx.getLogin());
                turnUsername = expiresEpochSec + ":" + prefix + "_" + safeLogin;
                turnPassword = makeTurnRestPassword(sharedSecret, turnUsername);
            } else if (!staticUsername.isBlank() && !staticPassword.isBlank()) {
                turnUsername = staticUsername;
                turnPassword = staticPassword;
            }
        }

        Net_GetCallIceConfig_Response resp = new Net_GetCallIceConfig_Response();
        resp.setOp(req.getOp());
        resp.setRequestId(req.getRequestId());
        resp.setStatus(WireCodes.Status.OK);
        resp.setStunUrls(stunUrls);
        resp.setTurnUrls(turnUrls);
        resp.setTurnUsername(turnUsername);
        resp.setTurnPassword(turnPassword);
        resp.setTurnServers(turnServers);
        resp.setTurnEnabled(!turnUrls.isEmpty() && !turnUsername.isBlank() && !turnPassword.isBlank());
        resp.setGeneratedAtMs(nowMs);
        resp.setExpiresAtMs(expiresAtMs);
        resp.setTtlSec(ttlSec);
        return resp;
    }

    private static List<Net_GetCallIceConfig_Response.TurnServerConfig> buildTurnServers(ConnectionContext ctx, long nowMs, int ttlSec) {
        List<Net_GetCallIceConfig_Response.TurnServerConfig> out = new ArrayList<>();
        for (int i = 1; i <= 16; i++) {
            String base = "call.ice.turn.servers." + i + ".";
            List<String> urls = parseUrls(readStr(base + "urls", ""));
            if (urls.isEmpty()) continue;

            String id = readStr(base + "id", "turn-" + i);
            String sharedSecret = readStr(base + "sharedSecret", "");
            String staticUsername = readStr(base + "username", "");
            String staticPassword = readStr(base + "password", "");
            String username = "";
            String password = "";
            if (!sharedSecret.isBlank()) {
                long expiresEpochSec = nowMs / 1000L + ttlSec;
                String prefix = readStr("call.ice.turn.userPrefix", "shine");
                String safeLogin = sanitizeLogin(ctx.getLogin());
                username = expiresEpochSec + ":" + prefix + "_" + safeLogin;
                password = makeTurnRestPassword(sharedSecret, username);
            } else if (!staticUsername.isBlank() && !staticPassword.isBlank()) {
                username = staticUsername;
                password = staticPassword;
            }
            if (username.isBlank() || password.isBlank()) continue;
            Net_GetCallIceConfig_Response.TurnServerConfig item = new Net_GetCallIceConfig_Response.TurnServerConfig();
            item.setId(id);
            item.setUrls(urls);
            item.setUsername(username);
            item.setPassword(password);
            out.add(item);
        }
        return out;
    }

    private static int readInt(String key, int fallback) {
        String value = CONFIG.getParam(key);
        if (value == null || value.isBlank()) return fallback;
        try {
            return Integer.parseInt(value.trim());
        } catch (Exception e) {
            return fallback;
        }
    }

    private static String readStr(String key, String fallback) {
        String value = CONFIG.getParam(key);
        if (value == null) return fallback;
        String trimmed = value.trim();
        return trimmed.isEmpty() ? fallback : trimmed;
    }

    private static int clampTtlSec(int ttlSec) {
        if (ttlSec < MIN_TTL_SEC) return MIN_TTL_SEC;
        return Math.min(ttlSec, MAX_TTL_SEC);
    }

    private static List<String> parseUrls(String raw) {
        Set<String> out = new LinkedHashSet<>();
        if (raw == null || raw.isBlank()) return new ArrayList<>();

        String[] chunks = raw.split("[,\\n\\r]+");
        for (String chunk : chunks) {
            String val = chunk == null ? "" : chunk.trim();
            if (val.isEmpty()) continue;
            out.add(val);
        }
        return new ArrayList<>(out);
    }

    private static String sanitizeLogin(String login) {
        String value = login == null ? "" : login.trim().toLowerCase(Locale.ROOT);
        if (value.isEmpty()) return "user";
        StringBuilder sb = new StringBuilder(value.length());
        for (int i = 0; i < value.length(); i++) {
            char ch = value.charAt(i);
            boolean ok = (ch >= 'a' && ch <= 'z')
                    || (ch >= '0' && ch <= '9')
                    || ch == '_' || ch == '-' || ch == '.';
            sb.append(ok ? ch : '_');
        }
        return sb.toString();
    }

    private static String makeTurnRestPassword(String sharedSecret, String username) {
        try {
            Mac mac = Mac.getInstance(HMAC_SHA1);
            SecretKeySpec keySpec = new SecretKeySpec(sharedSecret.getBytes(StandardCharsets.UTF_8), HMAC_SHA1);
            mac.init(keySpec);
            byte[] digest = mac.doFinal(username.getBytes(StandardCharsets.UTF_8));
            return Base64.getEncoder().encodeToString(digest);
        } catch (Exception e) {
            throw new IllegalStateException("Не удалось сгенерировать TURN пароль", e);
        }
    }
}
