package be.fgov.ehealth.technicalconnector.tests.junit.rule;

import be.ehealth.technicalconnector.config.ConfigFactory;
import be.ehealth.technicalconnector.config.ConfigValidator;
import be.ehealth.technicalconnector.config.impl.RecursiveProperties;
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.ConnectorCryptoUtils;
import be.ehealth.technicalconnector.utils.ConnectorIOUtils;
import be.ehealth.technicalconnector.utils.ConnectorXmlUtils;
import be.ehealth.technicalconnector.utils.DateUtils;
import be.fgov.ehealth.technicalconnector.bcp.EndpointUpdater;
import be.fgov.ehealth.technicalconnector.signature.AdvancedElectronicSignatureEnumeration;
import be.fgov.ehealth.technicalconnector.signature.SignatureBuilderFactory;
import be.fgov.ehealth.technicalconnector.signature.domain.XadesOption;
import be.fgov.ehealth.technicalconnector.test.tests.utils.LoggingUtils;
import org.apache.commons.io.IOUtils;
import org.bouncycastle.util.encoders.Hex;
import org.junit.rules.ExternalResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import java.io.File;
import java.io.FileOutputStream;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.UUID;

public class EndpointUpdateRule extends ExternalResource {

    private static final Logger LOG = LoggerFactory.getLogger(EndpointUpdateRule.class);

    private String location;

    public EndpointUpdateRule() {
    }

    public EndpointUpdateRule(String location) {
        this.location = location;
    }

    public void setLocation(String location) {
        this.location = location;
    }

    @Override
    protected void before() throws Throwable {
        LoggingUtils.bootstrap();

        String newLocation = sign(location);

        LOG.debug("Using new be.ehealth.technicalconnector.bcp.status.endpoint [{}]", newLocation);

        ConfigValidator config = ConfigFactory.getConfigValidator();
        config.setProperty("crypto.revocationstatuschecker.classname", "be.ehealth.technicalconnector.service.etee.impl.ConnectorMockRevocationStatusChecker");
        config.setProperty("be.ehealth.technicalconnector.bcp.status.endpoint", newLocation);
        config.setProperty("connector.soaphandler.connection.request.timeout", "1000");

        EndpointUpdater.update();
    }

    private String sign(String location) throws Exception {
        String id = "_" + UUID.randomUUID().toString();

        Element input = ConnectorXmlUtils.toDocument(ConnectorIOUtils.getResourceAsByteArray(location + ".xml")).getDocumentElement();
        input.setAttribute("Id", id);
        input.setAttribute("IssueInstant", DateUtils.printDateTime(Instant.now()));
        NodeList sigElements = input.getElementsByTagNameNS("http://www.w3.org/2000/09/xmldsig#", "Signature");
        for (int i = 0; i < sigElements.getLength(); i++) {
            input.removeChild(sigElements.item(0));
        }

        byte[] content = ConnectorXmlUtils.toByteArray(input);

        Credential cred = getCredential();

        byte[] signedContent = sign(id, content, cred);


        File xmlFile = File.createTempFile("servicelistv2-", ".xml");
        xmlFile.deleteOnExit();
        IOUtils.write(signedContent, new FileOutputStream(xmlFile));

        File sha2File = new File(xmlFile.getAbsolutePath()
                .replaceAll(".xml", ".sha2"));
        sha2File.deleteOnExit();
        IOUtils.write(Hex.encode(ConnectorCryptoUtils.calculateDigest("SHA-256", signedContent)), new FileOutputStream(sha2File));

        return xmlFile.getAbsolutePath()
                .replaceAll(".xml", "");

    }

    private byte[] sign(String id, byte[] content, Credential cred) throws TechnicalConnectorException {
        Map<String, Object> options = new HashMap<String, Object>();
        options.put(XadesOption.BASEURI, id);
        options.put(XadesOption.ENCAPSULATE, true);

        List<String> transformers = new ArrayList<String>();
        transformers.add("http://www.w3.org/2001/10/xml-exc-c14n#");

        options.put(XadesOption.TRANSFORMERLIST, transformers);


        return SignatureBuilderFactory.getSignatureBuilder(AdvancedElectronicSignatureEnumeration.XAdES)
                .sign(cred, content, options);
    }

    private Credential getCredential() throws Exception {
        Properties props = new RecursiveProperties();
        props.load(EndpointUpdateRule.class.getResourceAsStream("/be.ehealth.technicalconnector.test.properties"));

        KeyStore keystore = KeyStore.getInstance("PKCS12");
        keystore.load(ConnectorIOUtils.getResourceAsStream(props.getProperty("test.servicelist.signer.keystore.location")), props.getProperty("test.servicelist.signer.keystore.password").toCharArray());

        String alias = props.getProperty("test.servicelist.signer.privatekey.alias");
        PrivateKey key = (PrivateKey) keystore.getKey(alias, props.getProperty("test.servicelist.signer.privatekey.password").toCharArray());
        X509Certificate cert = (X509Certificate) keystore.getCertificate("authentication");

        return new KeyPairCredential(key, cert);
    }


    @Override
    protected void after() {
        EndpointUpdater.reset();
    }

}
