/*
 * Copyright (c) eHealth
 */
package be.fgov.ehealth.technicalconnector.ra.domain;

import java.awt.*;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.Properties;
import java.util.UUID;

import static be.fgov.ehealth.technicalconnector.tests.session.SessionInitializer.getSessionProperty;

import javax.security.auth.x500.X500Principal;

import be.fgov.ehealth.technicalconnector.tests.utils.AssertTools;
import org.apache.commons.lang.StringUtils;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.junit.*;

import be.ehealth.technicalconnector.exception.TechnicalConnectorException;
import be.ehealth.technicalconnector.session.Session;
import be.ehealth.technicalconnector.utils.ConnectorIOUtils;
import be.ehealth.technicalconnector.utils.IdentifierType;
import be.fgov.ehealth.technicalconnector.ra.builders.BuilderFactory;
import be.fgov.ehealth.technicalconnector.ra.enumaration.Language;
import be.fgov.ehealth.technicalconnector.ra.enumaration.RevocationReason;
import be.fgov.ehealth.technicalconnector.ra.enumaration.UsageType;
import be.fgov.ehealth.technicalconnector.ra.utils.CertificateUtils;
import be.fgov.ehealth.technicalconnector.tests.session.SessionInitializer;
import be.fgov.ehealth.technicalconnector.tests.utils.AssumeTools;


/**
 * Integration to generate
 *
 * @author EHP
 */
public class ContractIntegrationTest {


    private String phonePrivate;
    private String mailPrivate;
    private String mailGeneral;
    private String phoneGeneral;
    private X509Certificate orgCert;
    private X509Certificate persCert;

    @BeforeClass
    public static void before() throws Exception {
        AssumeTools.isEIDEnabled();
        SessionInitializer.init(false);
        Session.getInstance().loadEncryptionKeys(getSessionProperty("test.session.encryption.password"));
    }

    @Before
    public void init() throws Exception {
        phonePrivate = getSessionProperty("test.ra.private.phone");
        mailPrivate = getSessionProperty("test.ra.private.mail");
        mailGeneral = getSessionProperty("test.ra.general.mail");
        phoneGeneral = getSessionProperty("test.ra.general.phone");
        orgCert = generateCert(" O=Federal Government,OU=eHealth-platform Belgium,OU=APOTHEEL ACCOU NV,OU=NIHII-PHARMACY\\=34240406,C=BE,CN=NIHII-PHARMACY\\=34240406");
        persCert = generateCert("O=Federal Government,OU=eHealth-platform Belgium,OU=TEST PERSON,OU=SSIN\\=01020300156,C=BE,CN=SSIN\\=01020300156");
    }

    @Test
    public void newCertificateContractInDutchWithPrivateContactDataWithEidThroughConstructor() throws Exception {
        newCertificateContract(new ContactData(phonePrivate, mailPrivate, Language.NL), new DistinguishedName());
    }

    @Test
    public void newCertificateContractInDutchWithPrivateContactDataWithoutEidThroughConstructor() throws Exception {
         newCertificateContract(new ContactData(phonePrivate, mailPrivate, Language.NL), new DistinguishedName(persCert.getSubjectX500Principal()));
    }

    @Test
    public void newCertificateContractInFrenchWithPrivateContactDataWithEidThroughConstructor() throws Exception {
        newCertificateContract(new ContactData(phonePrivate, mailPrivate, Language.FR), new DistinguishedName());
    }

    @Test
    public void newCertificateContractInFrenchWithPrivateContactDataWithoutEidThroughConstructor() throws Exception {
        newCertificateContract(new ContactData(phonePrivate, mailPrivate, Language.FR), new DistinguishedName(persCert.getSubjectX500Principal()));
    }

    @Test
    public void newCertificateContractInFrenchWithPrivateAndGeneralContactDataWithEidThroughConstructor() throws Exception {
        newCertificateContract(new ContactData(mailGeneral, phoneGeneral, mailGeneral, mailPrivate, Language.FR), new DistinguishedName());
    }

    @Test
    public void newCertificateContractInDutchWithPrivateAndGeneralContactDataWithEidThroughConstructor() throws Exception {
        newCertificateContract(new ContactData(mailGeneral, phoneGeneral, mailGeneral, mailPrivate, Language.NL), new DistinguishedName());
    }

    @Test
    public void newCertificateContractInFrenchWithPrivateAndGeneralContactDataWithoutEidThroughConstructor() throws Exception {
        newCertificateContract(new ContactData(mailGeneral, phoneGeneral, mailGeneral, mailPrivate, Language.FR), new DistinguishedName(persCert.getSubjectX500Principal()));
    }

    @Test
    public void newCertificateContractInDutchWithPrivateAndGeneralContactDataWithoutEidThroughConstructor() throws Exception {
        newCertificateContract(new ContactData(mailGeneral, phoneGeneral, mailGeneral, mailPrivate, Language.NL), new DistinguishedName(persCert.getSubjectX500Principal()));
    }


    private static NewCertificateContract newCertificateContract(ContactData contact, DistinguishedName name) throws Exception{
        NewCertificateContract contract = new NewCertificateContract(name, contact, UsageType.CODAGE);
        view(contract);
        return contract;
    }

    private static RenewCertificateContract renewCertificateContract(X509Certificate cert, ContactData contact, DistinguishedName name)throws Exception{
        RenewCertificateContract contract = new RenewCertificateContract(cert, contact, UsageType.CODAGE);
        view(contract);
        return contract;
    }


    @Test
    public void generateCertificateIndividualNL() throws Exception {
        NewCertificateContract contract = BuilderFactory.newContractBuilder().create()//
                .withEid()//
                .withPrivatePhone(phonePrivate)//
                .withPrivateEmail(mailPrivate).useLanguage(Language.FR).build();
        view(contract);
    }

    @Test
    public void generateCertificateOrganization() throws Exception {
        NewCertificateContract contract = BuilderFactory.newContractBuilder().create()//
                .forOrganization().withId("89999865", IdentifierType.NIHII_LABO).withName("LABORATORY EHEALTH MOCK 2").withApplicationId(generatedRandomApplicationId())//
                .withPrivatePhone(phonePrivate)//
                .withPrivateEmail(mailPrivate).useLanguage(Language.NL).build();
        view(contract);
    }

    @Test
    public void renewPerson() throws Exception {
        NewCertificateContract contract = BuilderFactory.newContractBuilder().renew()//
                .withCert(persCert)//
                .withPrivatePhone(phonePrivate)//
                .withPrivateEmail(mailPrivate).useLanguage(Language.FR).build();

        view(contract);
    }

    @Test
    public void renewOrganisation() throws Exception {

        NewCertificateContract contract = BuilderFactory.newContractBuilder().renew()//
                .withCert(orgCert)//
                .withPrivatePhone(phonePrivate)//
                .withPrivateEmail(mailPrivate).useLanguage(Language.FR).build();

        view(contract);
    }

    @Test
    public void revokeOtherOrganisationFR() throws Exception {
        Contract contract = BuilderFactory.newContractBuilder().revoke()//
                .withCert(orgCert)//
                .withReason(RevocationReason.CA_POLICY_VIOLATED)//
                .useLanguage(Language.FR).build();

        view(contract);
    }

    @Test
    public void revokePassword() throws Exception {
        java.security.cert.X509Certificate cert = Session.getInstance().getSession().getEncryptionCredential().getCertificate();
        Contract contract = BuilderFactory.newContractBuilder().revoke()//
                .withCert(cert)//
                .withReason(RevocationReason.PASSWORD_COMPROMISED)//
                .useLanguage(Language.NL).build();
        view(contract);

    }

    @Test
    public void revokeOther() throws Exception {
        java.security.cert.X509Certificate cert = Session.getInstance().getSession().getEncryptionCredential().getCertificate();
        Contract contract = BuilderFactory.newContractBuilder().revoke().withCert(cert).withReason(RevocationReason.OTHER).withExplanation("unknown").useLanguage(Language.NL).build();

        view(contract);

    }

    private static void view(Contract contract) throws IOException {
        Assert.assertFalse(StringUtils.contains(contract.getContent(),"$"));
        if(!GraphicsEnvironment.isHeadless()) {
            AssumeTools.isUserInteractionEnabled();
            File htmlFile = File.createTempFile("contract", ".html");
            FileOutputStream fos = new FileOutputStream(htmlFile);
            try {
                fos.write(contract.getContract().getBytes());
            } finally {
                ConnectorIOUtils.closeQuietly(fos);
            }
            Desktop.getDesktop().browse(htmlFile.toURI());
        }
    }

    private static String generatedRandomApplicationId() {
        return UUID.randomUUID().toString().replace("-", "").substring(0, 29);
    }

    public static X509Certificate generateCert(String DN) throws TechnicalConnectorException {
        try {
            KeyPair key = CertificateUtils.generateKeyPair();
            X500Principal principal = new X500Principal(DN);


            X509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder(principal, new BigInteger("1"), new Date(), new Date(), principal, key.getPublic());

            int keyUsageDetails = KeyUsage.dataEncipherment;
            keyUsageDetails += KeyUsage.keyEncipherment;
            builder.addExtension(Extension.keyUsage, true, new KeyUsage(keyUsageDetails));

            ContentSigner signer = new JcaContentSignerBuilder("SHA1WithRSA").build(key.getPrivate());

            X509CertificateHolder holder = builder.build(signer);

            return new JcaX509CertificateConverter().setProvider("BC").getCertificate(holder);
        } catch (OperatorCreationException e) {
            throw new IllegalArgumentException(e);
        } catch (IOException e) {
            throw new IllegalArgumentException(e);
        } catch (CertificateException e) {
            throw new IllegalArgumentException(e);
        }

    }

}