package ru.zetit.bridgeandjoins.crypto;

import com.google.protobuf.ByteString;
import io.grpc.Status;
import io.grpc.StatusRuntimeException;
import ru.zetit.bridgeandjoins.crypto.v1.SignRequest;
import ru.zetit.bridgeandjoins.crypto.v1.SignResponse;

import java.util.logging.Logger;

/**
 * Хендлер подписи. Формирует CMS detached signature через
 * VCERT_CmsBlkDetSignMem с алгоритмом ГОСТ Р 34.10-2012 (256 бит
 * по умолчанию из профиля Валидаты).
 *
 * Поле key_alias из gRPC-запроса передаётся в certid_t.keyId — нативный
 * CSP подставит дефолтный ключ из профиля при пустом alias.
 *
 * Возвращает signed_xml = DER-байты CMS detached signature. На уровне
 * bj-server этот блок отправляется как signed_document (base64).
 * XMLDSig-обёртка с канонизацией добавится отдельным этапом
 * (Apache Santuario).
 */
public final class SignHandler {

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

    private final KeystoreProvider keystore;

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

    public SignResponse handle(SignRequest request) {
        if (!keystore.ready() || keystore.pki() == null) {
            throw new StatusRuntimeException(Status.FAILED_PRECONDITION
                    .withDescription("crypto-service: SignXMLDSig — провайдер "
                            + keystore.name() + " не готов: " + keystore.message()));
        }
        if (keystore instanceof ValidataProvider vp && vp.isMinimal()) {
            throw new StatusRuntimeException(Status.FAILED_PRECONDITION
                    .withDescription("crypto-service: SignXMLDSig — Валидата в режиме minimal "
                            + "(BJ_VALIDATA_PROFILE не задан), подпись недоступна без профиля"));
        }
        if (request.getPayload().isEmpty()) {
            throw new StatusRuntimeException(Status.INVALID_ARGUMENT
                    .withDescription("crypto-service: SignXMLDSig — payload пуст"));
        }

        Pki1.LocalIface pki = keystore.pki();
        try {
            Pki1.LocalIface.sign_param_t signParam = new Pki1.LocalIface.sign_param_t();
            String alias = request.getKeyAlias();
            if (alias != null && !alias.isEmpty()) {
                Pki1.LocalIface.certid_t cid = new Pki1.LocalIface.certid_t();
                cid.type = Pki1.LocalIface.ID_ALIAS;
                cid.alias = alias;
                signParam.mycert = cid;
            }

            Pki1.LocalIface.mem_blk_t data = memBlock(request.getPayload().toByteArray());
            Pki1.LocalIface.mem_blk_t signature = new Pki1.LocalIface.mem_blk_t();

            // CMS detached signature: подпись в signature, исходные данные
            // в результат не включаются. Третий аргумент (key) — null:
            // ключ подбирается нативным CSP по certid.
            int rc = pki.VCERT_CmsBlkDetSignMem(signParam, data, null, signature);
            if (rc != 0) {
                String err = pki.VCERT_GetErrorText(rc);
                throw new StatusRuntimeException(Status.INTERNAL
                        .withDescription("crypto-service: VCERT_CmsBlkDetSignMem rc=" + rc
                                + " (" + err + ")"));
            }
            if (signature.buf == null || signature.len <= 0) {
                throw new StatusRuntimeException(Status.INTERNAL
                        .withDescription("crypto-service: подпись вернулась пустая"));
            }
            return SignResponse.newBuilder()
                    .setSignedXml(ByteString.copyFrom(signature.buf, 0, signature.len))
                    .build();
        } catch (StatusRuntimeException sre) {
            throw sre;
        } catch (Throwable t) {
            LOG.severe("SignHandler: исключение " + t);
            throw new StatusRuntimeException(Status.INTERNAL
                    .withDescription("crypto-service: " + t.getClass().getSimpleName()
                            + ": " + t.getMessage()));
        }
    }

    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;
    }
}
