package org.jboss.resteasy.test.crypto;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.X509Certificate;

import jakarta.mail.MessagingException;
import jakarta.mail.internet.InternetHeaders;
import jakarta.mail.internet.MimeBodyPart;
import jakarta.mail.internet.MimeMultipart;
import jakarta.mail.util.ByteArrayDataSource;

import org.bouncycastle.cms.SignerInfoGenerator;
import org.bouncycastle.cms.SignerInformation;
import org.bouncycastle.cms.SignerInformationStore;
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoGeneratorBuilder;
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.mail.smime.SMIMESigned;
import org.bouncycastle.mail.smime.SMIMESignedGenerator;
import org.jboss.logging.Logger;
import org.jboss.resteasy.security.DerUtils;
import org.jboss.resteasy.security.PemUtils;
import org.jboss.resteasy.utils.TestUtil;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

/**
 * @tpSubChapter Crypto
 * @tpChapter Unit tests
 * @tpTestCaseDetails Test for sign by X509Certificate.
 * @tpSince RESTEasy 3.0.16
 */
public class SignedTest {

    protected static final Logger logger = Logger.getLogger(SignedTest.class.getName());
    private static X509Certificate cert;
    private static PrivateKey privateKey;
    private static PrivateKey badKey;
    static final String certPemPath;
    static final String certPrivatePemPath;
    static final String certPrivateKeyDerPath;
    static final String pythonPath;

    static {
        String base = TestUtil.getResourcePath(SignedTest.class, "");
        certPemPath = new StringBuilder().append(base).append("SignedMycert.pem").toString();
        certPrivatePemPath = new StringBuilder().append(base).append("MycertPrivate.pem").toString();
        certPrivateKeyDerPath = new StringBuilder().append(base).append("SignedPrivateDkimKeyDer.der").toString();
        pythonPath = new StringBuilder().append(base).append("SignedPython.txt").toString();
    }

    @BeforeAll
    public static void setup() throws Exception {
        Security.addProvider(new BouncyCastleProvider());
        InputStream certIs = new FileInputStream(certPemPath);
        cert = PemUtils.decodeCertificate(certIs);

        InputStream privateIs = new FileInputStream(certPrivatePemPath);
        privateKey = PemUtils.decodePrivateKey(privateIs);

        InputStream badIs = new FileInputStream(certPrivateKeyDerPath);
        badKey = DerUtils.decodePrivateKey(badIs);
    }

    private MimeBodyPart createMsg() throws MessagingException {
        InternetHeaders ih = new InternetHeaders();
        ih.addHeader("Content-Type", "application/xml");
        return new MimeBodyPart(ih, "<customer name=\"bill\"/>".getBytes());
    }

    private void output(MimeBodyPart mp) throws IOException, MessagingException {
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        mp.writeTo(os);
        String s = new String(os.toByteArray());
        logger.info(s);
    }

    private void output(MimeMultipart mp) throws IOException, MessagingException {
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        mp.writeTo(os);
        String s = new String(os.toByteArray());
        logger.info(s);
    }

    /**
     * @tpTestDetails Multipart test
     * @tpSince RESTEasy 3.0.16
     */
    @Test
    public void testMultipart() throws Exception {
        MimeMultipart mp = new MimeMultipart();
        InternetHeaders ih = new InternetHeaders();
        ih.addHeader("Content-Type", "text/xml");
        MimeBodyPart bp = new MimeBodyPart(ih, "<customer/>".getBytes());
        mp.addBodyPart(bp);

        bp = new MimeBodyPart(ih, "<product/>".getBytes());
        mp.addBodyPart(bp);

        ByteArrayOutputStream os = new ByteArrayOutputStream();
        mp.writeTo(os);
        String s = new String(os.toByteArray());

        logger.info(s);
        logger.info("************");

        String contentType = mp.getContentType();
        contentType = contentType.replace("\r\n", "").replace("\t", " ");
        logger.info("Content-Type: " + contentType);

        mp = new MimeMultipart(new ByteArrayDataSource(s.getBytes(), "multipart/signed"));
        logger.info("count: " + mp.getCount());
    }

    /**
     * @tpTestDetails Test for python sign format
     * @tpSince RESTEasy 3.0.16
     */
    @Test
    public void testPythonSigned() throws Exception {
        final InputStream pythonIs = new FileInputStream(pythonPath);

        ByteArrayDataSource ds = new ByteArrayDataSource(pythonIs, "multipart/signed");
        MimeMultipart mm = new MimeMultipart(ds);

        logger.info(mm.getContentType());

        logger.info("Multipart.count(): " + mm.getCount());

        MimeBodyPart mbp = (MimeBodyPart) mm.getBodyPart(0);

        output(mbp);

        SMIMESigned signed = new SMIMESigned(mm);

        SignerInformationStore signers = signed.getSignerInfos();
        Assertions.assertEquals(1, signers.size(), "Wrong count of signers");
        SignerInformation signer = signers.getSigners().iterator().next();
        Assertions.assertTrue(
                signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert.getPublicKey())),
                "Unsuccessful verification of signer");
    }

    /**
     * @tpTestDetails Check output after signing
     * @tpSince RESTEasy 3.0.16
     */
    @Test
    public void testOutput() throws Exception {
        {
            SMIMESignedGenerator gen = new SMIMESignedGenerator();
            SignerInfoGenerator signer = new JcaSimpleSignerInfoGeneratorBuilder().setProvider("BC").build("SHA1WITHRSA",
                    privateKey, cert);
            gen.addSignerInfoGenerator(signer);

            MimeMultipart mp = gen.generate(createMsg());
            output(mp);
        }

        {
            SMIMESignedGenerator gen = new SMIMESignedGenerator();
            SignerInfoGenerator signer = new JcaSimpleSignerInfoGeneratorBuilder().setProvider("BC").build("SHA1WITHRSA",
                    privateKey, cert);
            gen.addSignerInfoGenerator(signer);

            MimeMultipart mp = gen.generate(createMsg());

            ByteArrayOutputStream os = new ByteArrayOutputStream();
            mp.writeTo(os);

            ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
            String contentType = mp.getContentType();
            contentType = contentType.replace("\r\n", "").replace("\t", " ");

            ByteArrayDataSource ds = new ByteArrayDataSource(is, contentType);
            MimeMultipart mm = new MimeMultipart(ds);
            MimeBodyPart part = (MimeBodyPart) mm.getBodyPart(0);
        }
    }

    /**
     * @tpTestDetails Test python-style verification
     * @tpSince RESTEasy 3.0.16
     */
    @Test
    public void testPythonVerified() throws Exception {
        SMIMESignedGenerator gen = new SMIMESignedGenerator();
        SignerInfoGenerator signer = new JcaSimpleSignerInfoGeneratorBuilder().setProvider("BC").build("SHA1WITHRSA",
                privateKey, cert);
        gen.addSignerInfoGenerator(signer);

        MimeMultipart mp = gen.generate(createMsg());
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        mp.writeTo(os);
        String contentType = mp.getContentType();
        contentType = contentType.replace("\r\n", "").replace("\t", " ");
        logger.info(contentType);
        String s = new String(os.toByteArray());
        StringBuilder builder = new StringBuilder();
        builder.append("Content-Type: ").append(contentType).append("\r\n\r\n").append(s);
        String output = builder.toString();

        FileOutputStream fp = new FileOutputStream("target/smime_signed.txt");
        fp.write(output.getBytes());
        fp.close();
    }

    /**
     * @tpTestDetails Test python-style wrong verification
     * @tpSince RESTEasy 3.0.16
     */
    @Test
    public void testPythonVerifiedBad() throws Exception {
        SMIMESignedGenerator gen = new SMIMESignedGenerator();
        SignerInfoGenerator signer = new JcaSimpleSignerInfoGeneratorBuilder().setProvider("BC").build("SHA1WITHRSA", badKey,
                cert);
        gen.addSignerInfoGenerator(signer);

        MimeMultipart mp = gen.generate(createMsg());
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        mp.writeTo(os);
        String contentType = mp.getContentType();
        contentType = contentType.replace("\r\n", "").replace("\t", " ");
        logger.info(contentType);
        String s = new String(os.toByteArray());
        StringBuilder builder = new StringBuilder();
        builder.append("Content-Type: ").append(contentType).append("\r\n\r\n").append(s);
        String output = builder.toString();

        FileOutputStream fp = new FileOutputStream("target/smime_signed_bad.txt");
        fp.write(output.getBytes());
        fp.close();
    }
}
