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

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
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.auth.entyties.Net_StartEspPairing_Request;
import server.logic.ws_protocol.JSON.handlers.auth.entyties.Net_StartEspPairing_Response;
import server.logic.ws_protocol.JSON.push.WsEventSender;
import server.logic.ws_protocol.JSON.utils.AuthKeyUtils;
import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory;
import server.logic.ws_protocol.JSON.utils.NetIdGenerator;
import server.logic.ws_protocol.WireCodes;
import shine.db.dao.EspPairingRequestsDAO;
import shine.db.dao.EspPairingSettingsDAO;
import shine.db.dao.SolanaUsersDAO;
import shine.db.entities.EspPairingRequestEntry;
import shine.db.entities.EspPairingSettingsEntry;
import shine.db.entities.SolanaUserEntry;

import java.util.List;

public class Net_StartEspPairing_Handler implements JsonMessageHandler {

    private static final ObjectMapper MAPPER = new ObjectMapper();

    @Override
    public Net_Response handle(Net_Request baseReq, ConnectionContext ctx) throws Exception {
        Net_StartEspPairing_Request req = (Net_StartEspPairing_Request) baseReq;

        String login = req.getLogin() == null ? "" : req.getLogin().trim();
        if (login.isBlank()) {
            return NetExceptionResponseFactory.error(req, WireCodes.Status.BAD_REQUEST, "EMPTY_LOGIN", "Пустой login");
        }

        String requesterSessionKey = req.getRequesterSessionKey();
        if (requesterSessionKey == null || requesterSessionKey.isBlank()) {
            return NetExceptionResponseFactory.error(req, WireCodes.Status.BAD_REQUEST, "EMPTY_REQUESTER_SESSION_KEY", "Пустой requesterSessionKey");
        }
        try {
            requesterSessionKey = AuthKeyUtils.normalize(requesterSessionKey, "requesterSessionKey");
            AuthKeyUtils.parseEd25519PublicKey(requesterSessionKey, "requesterSessionKey");
        } catch (Exception e) {
            return NetExceptionResponseFactory.error(req, WireCodes.Status.BAD_REQUEST, "BAD_REQUESTER_SESSION_KEY", "Некорректный requesterSessionKey");
        }

        int requesterSessionType = AuthSessionTypeSupport.normalizeRequestedSessionType(req.getRequesterSessionType());
        if (!AuthSessionTypeSupport.isSupportedSessionType(requesterSessionType)) {
            return NetExceptionResponseFactory.error(req, WireCodes.Status.BAD_REQUEST, "BAD_SESSION_TYPE", "Неподдерживаемый requesterSessionType");
        }
        int payloadType = EspPairingSupport.normalizePayloadType(req.getPayloadType());
        if (!EspPairingSupport.isSupportedPayloadType(payloadType)) {
            return NetExceptionResponseFactory.error(req, WireCodes.Status.BAD_REQUEST, "BAD_PAYLOAD_TYPE", "payloadType должен быть 1, 2 или 3");
        }
        String passwordHash = EspPairingSupport.normalizeOpaqueHash(req.getPasswordHash());
        if (passwordHash == null) {
            return NetExceptionResponseFactory.error(req, WireCodes.Status.BAD_REQUEST, "EMPTY_PASSWORD_HASH", "Пустой passwordHash");
        }

        SolanaUserEntry user = SolanaUsersDAO.getInstance().getByLogin(login);
        if (user == null) {
            return NetExceptionResponseFactory.error(req, 422, "PAIRING_NOT_AVAILABLE", "Для этого login pairing недоступен");
        }
        String canonicalLogin = user.getLogin();

        EspPairingSettingsEntry settings = EspPairingSettingsDAO.getInstance().getByLogin(canonicalLogin);
        if (settings == null || !settings.isEnabled() || settings.getPasswordHash() == null || settings.getPasswordHash().isBlank()) {
            return NetExceptionResponseFactory.error(req, 422, "PAIRING_NOT_AVAILABLE", "Для этого login pairing недоступен");
        }

        long now = System.currentTimeMillis();
        EspPairingRequestsDAO.getInstance().expirePending(now);
        if (settings.getBlockedUntilMs() > now) {
            return NetExceptionResponseFactory.error(req, EspPairingSupport.STATUS_PAIRING_RATE_LIMIT, "PAIRING_RATE_LIMITED", "Временная блокировка pairing по числу неудачных попыток");
        }
        int recentAttempts = EspPairingRequestsDAO.getInstance().countRecentByLoginAndStatuses(
                canonicalLogin,
                now - EspPairingSupport.REQUEST_RATE_WINDOW_MS,
                EspPairingSupport.STATE_CREATED,
                EspPairingSupport.STATE_REJECTED
        );
        if (recentAttempts >= EspPairingSupport.REQUEST_RATE_LIMIT) {
            return NetExceptionResponseFactory.error(req, EspPairingSupport.STATUS_PAIRING_RATE_LIMIT, "PAIRING_RATE_LIMITED", "Слишком много pairing-запросов за короткое время");
        }
        if (!settings.getPasswordHash().equals(passwordHash)) {
            return NetExceptionResponseFactory.error(req, 422, "PAIRING_PASSWORD_INVALID", "Неверный pairing-пароль");
        }

        String clientPlatform = AuthSessionTypeSupport.normalizeClientPlatform(req.getRequesterClientPlatform());
        int ttlSeconds = EspPairingSupport.normalizeTtlSeconds(settings.getTtlSeconds());
        EspPairingSupport.PairingFingerprint fingerprint = EspPairingSupport.deriveFingerprint(
                canonicalLogin,
                requesterSessionKey,
                requesterSessionType,
                clientPlatform,
                payloadType,
                now
        );

        EspPairingRequestEntry entry = new EspPairingRequestEntry();
        entry.setPairingId(EspPairingSupport.newPairingId());
        entry.setLogin(canonicalLogin);
        entry.setRequesterSessionKey(requesterSessionKey);
        entry.setRequesterSessionType(requesterSessionType);
        entry.setRequesterClientPlatform(clientPlatform);
        entry.setPayloadType(payloadType);
        entry.setStatus(EspPairingSupport.STATE_CREATED);
        entry.setShortCode(fingerprint.shortCode());
        entry.setFingerprintB58(fingerprint.fingerprintB58());
        entry.setCreatedAtMs(now);
        entry.setExpiresAtMs(now + ttlSeconds * 1000L);
        entry.setUpdatedAtMs(now);
        entry.setDeliveredToHomeserver(false);
        EspPairingRequestsDAO.getInstance().insert(entry);

        List<ConnectionContext> approverConnections = EspPairingSupport.findOnlineTrustedConnections(canonicalLogin);
        boolean delivered = false;
        for (ConnectionContext targetCtx : approverConnections) {
            String eventId = NetIdGenerator.eventId("pair");
            ObjectNode payload = MAPPER.createObjectNode();
            payload.put("pairingId", entry.getPairingId());
            payload.put("login", canonicalLogin);
            payload.put("requesterSessionKey", requesterSessionKey);
            payload.put("requesterSessionType", requesterSessionType);
            payload.put("requesterClientPlatform", clientPlatform);
            payload.put("payloadType", payloadType);
            payload.put("shortCode", entry.getShortCode());
            payload.put("fingerprintB58", entry.getFingerprintB58());
            payload.put("createdAtMs", entry.getCreatedAtMs());
            payload.put("expiresAtMs", entry.getExpiresAtMs());
            delivered |= WsEventSender.sendEvent(targetCtx, "IncomingEspPairingRequest", eventId, payload);
        }
        if (delivered) {
            EspPairingRequestsDAO.getInstance().updateDeliveryFlag(entry.getPairingId(), true, System.currentTimeMillis());
        }

        Net_StartEspPairing_Response resp = new Net_StartEspPairing_Response();
        resp.setOp(req.getOp());
        resp.setRequestId(req.getRequestId());
        resp.setStatus(WireCodes.Status.OK);
        resp.setPairingId(entry.getPairingId());
        resp.setState(entry.getStatus());
        resp.setShortCode(entry.getShortCode());
        resp.setFingerprintB58(entry.getFingerprintB58());
        resp.setExpiresAtMs(entry.getExpiresAtMs());
        resp.setTrustedSessionOnline(!approverConnections.isEmpty());
        return resp;
    }
}
