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

import java.awt.Desktop;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringReader;
import java.security.KeyPair;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.UUID;

import org.apache.commons.io.IOUtils;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;

import be.ehealth.technicalconnector.exception.TechnicalConnectorException;
import be.ehealth.technicalconnector.service.sts.security.Credential;
import be.ehealth.technicalconnector.service.sts.security.impl.KeyPairCredential;
import be.ehealth.technicalconnector.utils.CertificateParser;
import be.ehealth.technicalconnector.utils.IdentifierType;
import be.fgov.ehealth.technicalconnector.ra.builders.BuilderFactory;
import be.fgov.ehealth.technicalconnector.ra.domain.Contract;
import be.fgov.ehealth.technicalconnector.ra.domain.NewCertificateContract;
import be.fgov.ehealth.technicalconnector.ra.domain.Organization;
import be.fgov.ehealth.technicalconnector.ra.domain.Result;
import be.fgov.ehealth.technicalconnector.ra.domain.RevokeCertificateContract;
import be.fgov.ehealth.technicalconnector.ra.enumaration.Language;
import be.fgov.ehealth.technicalconnector.ra.enumaration.RevocationReason;
import be.fgov.ehealth.technicalconnector.ra.enumaration.Status;
import be.fgov.ehealth.technicalconnector.ra.exceptions.RaException;
import be.fgov.ehealth.technicalconnector.ra.service.AuthenticationCertificateRegistrationService;
import be.fgov.ehealth.technicalconnector.ra.service.EncryptionTokenRegistrationService;
import be.fgov.ehealth.technicalconnector.ra.service.ServiceFactory;
import be.fgov.ehealth.technicalconnector.ra.utils.CertificateUtils;
import be.fgov.ehealth.technicalconnector.ra.utils.KeyStoreUtils;
import be.fgov.ehealth.technicalconnector.tests.junit.rule.SessionRule;
import be.fgov.ehealth.technicalconnector.tests.utils.AssumeTools;


/**
 * Integration test class to show how the RA module works
 * 
 * @author eHealth Platform
 */
public class RaWalkThroughIntegrationTest {

    private static final String TEMP_DIR = System.getProperty("java.io.tmpdir");

    @Rule
    public SessionRule rule = SessionRule.withInactiveSession().build();

    @Test
    public void listOrganization() throws Exception {
        AssumeTools.isUser(get("test.ra.organization.user.ssin"));
        List<Organization> orgList = BuilderFactory.newInformationBuilder().listAssociatedOrganizations().forCurrentIdentity();
        Assert.assertEquals(6, orgList.size());
    }

    @Test
    public void listRevocables() throws Exception {
        AssumeTools.isUser(get("test.ra.organization.user.ssin"));
        List<X509Certificate> certList = BuilderFactory.newInformationBuilder().listRevocableCertificates().forCurrentIdentity();
        Assert.assertEquals(9, certList.size());
    }

    @Test
    public void listApplicationIds() throws Exception {
        Organization organization = new Organization("0809394427", IdentifierType.CBE, "EHP");
        List<String> applicationIdList = BuilderFactory.newInformationBuilder().listActiveApplicationIds().forOrganization(organization);
        Assert.assertEquals(6, applicationIdList.size());
    }

    @Test(expected = RaException.class)
    public void walkEID() throws Exception {
        KeyPair authenticationKeyPair = CertificateUtils.generateKeyPair();

        NewCertificateContract contract = BuilderFactory.newContractBuilder().create()//
            .withEid()//
            .withPrivatePhone(get("test.ra.private.phone")).withPrivateEmail(get("test.ra.private.mail"))//
            .generatePKCS10(authenticationKeyPair)//
            .useLanguage(Language.NL)//
            .build();

        walk(contract, authenticationKeyPair);

    }

    @Test
    public void walkOrganization00() throws Exception {
        AssumeTools.isUser(get("test.ra.organization.user.ssin"));

        KeyPair authenticationKeyPair = CertificateUtils.generateKeyPair();

        NewCertificateContract contract = BuilderFactory.newContractBuilder().create()//
            .forOrganization().withId(get("test.ra.organization.id"), IdentifierType.NIHII_LABO).withName(get("test.ra.organization.name")).withApplicationId(UUID.randomUUID().toString().replace("-", "").substring(0, 29))//
            .withPrivatePhone(get("test.ra.private.phone")).withPrivateEmail(get("test.ra.private.mail"))//
            .generatePKCS10(authenticationKeyPair)//
            .useLanguage(Language.NL)//
            .build();

        walk(contract, authenticationKeyPair);

    }

    @Test
    public void walkOrganization01() throws Exception {
        AssumeTools.isUser(get("test.ra.organization.user.ssin"));

        KeyPair authenticationKeyPair = CertificateUtils.generateKeyPair();

        NewCertificateContract contract = BuilderFactory.newContractBuilder().create()//
            .forOrganization().withId(get("test.ra.organization.id"), IdentifierType.NIHII_LABO).withName(get("test.ra.organization.name")).withApplicationId(UUID.randomUUID().toString().replace("-", "").substring(0, 29))//
            .withPrivatePhone(get("test.ra.private.phone")).withPrivateEmail(get("test.ra.private.mail")).withGeneralPhone(get("test.ra.general.phone")).withGeneralEmail(get("test.ra.generalemail"))//
            .generatePKCS10(authenticationKeyPair)//
            .useLanguage(Language.NL)//
            .build();

        walk(contract, authenticationKeyPair);

    }

    @Test
    public void revoke() throws Exception {
        AssumeTools.isUser(get("test.ra.organization.user.ssin"));
        List<X509Certificate> certList = BuilderFactory.newInformationBuilder().listRevocableCertificates().forCurrentIdentity();
        for (X509Certificate cert : certList) {
            CertificateParser parser = new CertificateParser(cert);
            String id = get("test.ra.organization.id");
            if (parser.getId().equals(id) && parser.getIdentifier() == IdentifierType.NIHII_LABO && parser.getApplication().matches("[A-Z0-9]{29}")) {
                RevokeCertificateContract contract = BuilderFactory.newContractBuilder().revoke().withCert(cert).withReason(RevocationReason.PASSWORD_COMPROMISED).useLanguage(Language.NL).build();
                contract.getContract();
                ServiceFactory.getAuthenticationCertificateRegistrationService().revoke(contract);
            }
        }
    }

    public void walk(NewCertificateContract contract, KeyPair authenticationKeyPair) throws Exception {

        char[] passwd = "15".toCharArray();

        view(contract);

        String storeLocation = TEMP_DIR + contract.getDistinguishedName().asNormalisedBaseFileName();

        AuthenticationCertificateRegistrationService certRA = ServiceFactory.getAuthenticationCertificateRegistrationService();
        String requestId = certRA.request(contract);

        KeyStoreUtils store = new KeyStoreUtils();
        store.addAuthenticationKeyPair(authenticationKeyPair, passwd);
        store.store(storeLocation, passwd);

        Result<X509Certificate[]> result = poll(certRA, requestId);

        store.addAuthenticationChain(passwd, result.getResult());
        store.store(storeLocation, passwd);


        Credential cred = new KeyPairCredential(authenticationKeyPair.getPrivate(), result.getResult()[0]);
        EncryptionTokenRegistrationService etkRA = ServiceFactory.getEncryptionTokenRegistrationService(cred);

        KeyPair etkKeyPair = CertificateUtils.generateKeyPair();

        byte[] challenge = etkRA.registerPublicKey(etkKeyPair.getPublic());

        store.addEncryptionToken(etkKeyPair, passwd, CertificateUtils.generateCert(etkKeyPair));

        store.store(storeLocation, passwd);

        X509Certificate etkCert = BuilderFactory.newEncryptionTokenBuilder(cred).create()//
            .withKeyPair(etkKeyPair)//
            .withChallenge(challenge)//
            .build();

        etkRA.registerToken(etkCert.getEncoded());

        store.addEncryptionToken(etkKeyPair, passwd, etkCert);

        store.store(storeLocation, passwd);

    }


    private Result<X509Certificate[]> poll(AuthenticationCertificateRegistrationService certRA, String requestId) throws TechnicalConnectorException, InterruptedException {
        Result<X509Certificate[]> result = certRA.poll(requestId);
        while (result.getStatus().equals(Status.PENDING)) {
            long duration = new Duration(new DateTime(), result.getTime()).getMillis();
            if (duration > 0) {
                Thread.sleep(duration);
            }
            result = certRA.poll(requestId);
        }
        if (result.hasStatusError()) {
            throw new IllegalArgumentException(result.getStatus().name());
        }
        return result;
    }

    private static void view(Contract contract) throws IOException {
        File htmlFile = File.createTempFile("contract", ".html");
        htmlFile.deleteOnExit();
        IOUtils.copy(new StringReader(contract.getContract()), new FileWriter(htmlFile));
        Desktop.getDesktop().browse(htmlFile.toURI());
    }

    private String get(String key) {
        return rule.getSessionProperty(key);
    }

}
