package ru.zetit.bridgeandjoins.crypto;

import io.grpc.Status;
import io.grpc.StatusRuntimeException;
import ru.zetit.bridgeandjoins.crypto.v1.VerifyRequest;
import ru.zetit.bridgeandjoins.crypto.v1.VerifyResponse;

import java.util.logging.Logger;

/**
 * Хендлер проверки подписи. Обрабатывает CMS detached signature
 * (DER-байты в payload, само сообщение должно быть в request.message,
 * но пока контракт упрощён: payload = весь подписанный CMS-блок,
 * проверяется как attached-вариант).
 *
 * Внутри: VCERT_CmsBlkAttVerifyMem с verify_param_t. Возвращает
 * данные о подписанте: CN, ИНН (OID 1.2.643.3.131.1.1), серийник,
 * срок действия.
 *
 * Для XMLDSig-входных сообщений (как у НРД) нужен слой канонизации
 * XML и извлечения SignatureValue + KeyInfo — это отдельный этап.
 */
public final class VerifyHandler {

    private static final Logger LOG = Logger.getLogger(VerifyHandler.class.getName());

    private final KeystoreProvider keystore;

    public VerifyHandler(KeystoreProvider keystore) {
        this.keystore = keystore;
    }

    public VerifyResponse handle(VerifyRequest request) {
        if (!keystore.ready() || keystore.pki() == null) {
            return VerifyResponse.newBuilder()
                    .setValid(false)
                    .addErrors("crypto-service: провайдер " + keystore.name()
                            + " не готов: " + keystore.message())
                    .build();
        }
        if (request.getPayload().isEmpty()) {
            throw new StatusRuntimeException(Status.INVALID_ARGUMENT
                    .withDescription("crypto-service: VerifyXMLDSig — payload пуст"));
        }

        Pki1.LocalIface pki = keystore.pki();
        try {
            Pki1.LocalIface.verify_param_t verifyParam = new Pki1.LocalIface.verify_param_t();
            Pki1.LocalIface.mem_blk_t signedMessage = memBlock(request.getPayload().toByteArray());
            Pki1.LocalIface.mem_blk_t recoveredData = new Pki1.LocalIface.mem_blk_t();
            Pki1.LocalIface.verify_result_t result = new Pki1.LocalIface.verify_result_t();

            int rc = pki.VCERT_CmsBlkAttVerifyMem(verifyParam, signedMessage, recoveredData, result);
            if (rc != 0) {
                String err = pki.VCERT_GetErrorText(rc);
                return VerifyResponse.newBuilder()
                        .setValid(false)
                        .addErrors("VCERT_CmsBlkAttVerifyMem rc=" + rc + " (" + err + ")")
                        .build();
            }

            VerifyResponse.Builder resp = VerifyResponse.newBuilder().setValid(true);
            // Если есть инфа о подписанте — кладём её. Структура
            // verify_result_t содержит cms_siginf и/или certificate;
            // поля защищены от NPE, потому что нативный API заполняет
            // что есть.
            extractSignerInfo(pki, result, resp);
            return resp.build();
        } catch (Throwable t) {
            LOG.severe("VerifyHandler: исключение " + t);
            return VerifyResponse.newBuilder()
                    .setValid(false)
                    .addErrors(t.getClass().getSimpleName() + ": " + t.getMessage())
                    .build();
        }
    }

    /**
     * Извлекает CN/ИНН/серийник/сроки из verify_result_t.
     * verify_result_t.signs[i].cert — certificate_t подписанта.
     * Берём первого подписанта (signs[0]) — для НРД-обмена этого
     * достаточно; multi-signer обработка — отдельно.
     */
    private static void extractSignerInfo(Pki1.LocalIface pki,
                                          Pki1.LocalIface.verify_result_t result,
                                          VerifyResponse.Builder resp) {
        if (result == null || result.signs == null || result.signs.length == 0) {
            return;
        }
        Pki1.LocalIface.sign_status_t s = result.signs[0];
        if (s == null || s.cert == null) {
            return;
        }
        Pki1.LocalIface.certificate_t cert = s.cert;
        if (cert.subject != null) {
            resp.setSignerCn(extractRdn(cert.subject, "CN"));
            String inn = extractRdn(cert.subject, "INN");
            if (inn.isEmpty()) {
                inn = extractRdn(cert.subject, "1.2.643.3.131.1.1");
            }
            resp.setSignerInn(inn);
        }
        if (cert.serialNumber != null) {
            resp.setSerial(cert.serialNumber);
        }
        // notBefore/notAfter — unix epoch seconds.
        resp.setNotBefore((long) cert.notBefore);
        resp.setNotAfter((long) cert.notAfter);
    }

    /**
     * Извлекает значение RDN из строки subject вида
     * "CN=Иванов И.И.,OU=...,O=...,INN=7707083893".
     * Возвращает "" если RDN не найден.
     */
    private static String extractRdn(String dn, String rdn) {
        if (dn == null || rdn == null) return "";
        String needle = rdn + "=";
        int idx = dn.indexOf(needle);
        if (idx < 0) return "";
        int start = idx + needle.length();
        // Конец значения — следующая запятая (не экранированная). Простой
        // парсинг — достаточно для типовых cert-полей НРД/УЦ МБ.
        int end = dn.indexOf(',', start);
        if (end < 0) end = dn.length();
        return dn.substring(start, end).trim();
    }

    private static Pki1.LocalIface.mem_blk_t memBlock(byte[] bytes) {
        Pki1.LocalIface.mem_blk_t mb = new Pki1.LocalIface.mem_blk_t();
        mb.buf = bytes;
        mb.len = bytes.length;
        return mb;
    }
}
