/*
 * Copyright (c) 2019 the Eclipse Milo Authors
 *
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 */

package org.eclipse.milo.opcua.sdk.server;

import java.security.KeyPair;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;

import com.google.common.primitives.Bytes;
import org.eclipse.milo.opcua.stack.core.StatusCodes;
import org.eclipse.milo.opcua.stack.core.UaException;
import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy;
import org.eclipse.milo.opcua.stack.core.types.builtin.ByteString;
import org.eclipse.milo.opcua.stack.core.types.enumerated.MessageSecurityMode;
import org.jetbrains.annotations.Nullable;

public final class SecurityConfiguration {

    private final SecurityPolicy securityPolicy;
    private final MessageSecurityMode securityMode;
    private final KeyPair keyPair;
    private final X509Certificate serverCertificate;
    private final List<X509Certificate> serverCertificateChain;
    private final X509Certificate clientCertificate;
    private final List<X509Certificate> clientCertificateChain;

    public SecurityConfiguration(
        SecurityPolicy securityPolicy,
        MessageSecurityMode securityMode,
        @Nullable KeyPair keyPair,
        @Nullable X509Certificate serverCertificate,
        @Nullable List<X509Certificate> serverCertificateChain,
        @Nullable X509Certificate clientCertificate,
        @Nullable List<X509Certificate> clientCertificateChain) {

        this.securityPolicy = securityPolicy;
        this.securityMode = securityMode;
        this.keyPair = keyPair;
        this.serverCertificate = serverCertificate;
        this.serverCertificateChain = serverCertificateChain;
        this.clientCertificate = clientCertificate;
        this.clientCertificateChain = clientCertificateChain;
    }

    public SecurityPolicy getSecurityPolicy() {
        return securityPolicy;
    }

    public MessageSecurityMode getSecurityMode() {
        return securityMode;
    }

    @Nullable
    public KeyPair getKeyPair() {
        return keyPair;
    }

    @Nullable
    public X509Certificate getServerCertificate() {
        return serverCertificate;
    }

    @Nullable
    public List<X509Certificate> getServerCertificateChain() {
        return serverCertificateChain;
    }

    @Nullable
    public X509Certificate getClientCertificate() {
        return clientCertificate;
    }

    @Nullable
    public List<X509Certificate> getClientCertificateChain() {
        return clientCertificateChain;
    }

    public ByteString getClientCertificateBytes() throws UaException {
        return getCertificateBytes(getClientCertificate());
    }

    public ByteString getClientCertificateChainBytes() throws UaException {
        return getCertificateChainBytes(getClientCertificateChain());
    }

    public ByteString getServerCertificateBytes() throws UaException {
        return getCertificateBytes(getServerCertificate());
    }

    public ByteString getServerCertificateChainBytes() throws UaException {
        return getCertificateChainBytes(getServerCertificateChain());
    }

    private static ByteString getCertificateBytes(
        @Nullable X509Certificate certificate) throws UaException {

        if (certificate == null) {
            return ByteString.NULL_VALUE;
        } else {
            try {
                return ByteString.of(certificate.getEncoded());
            } catch (CertificateEncodingException e) {
                throw new UaException(StatusCodes.Bad_CertificateInvalid, e);
            }
        }
    }

    private static ByteString getCertificateChainBytes(
        @Nullable List<X509Certificate> certificateChain) throws UaException {

        if (certificateChain == null) {
            return ByteString.NULL_VALUE;
        } else {
            List<byte[]> certificates = new ArrayList<>(certificateChain.size());

            for (X509Certificate certificate : certificateChain) {
                try {
                    certificates.add(certificate.getEncoded());
                } catch (CertificateEncodingException e) {
                    throw new UaException(StatusCodes.Bad_CertificateInvalid, e);
                }
            }

            byte[] encoded = certificates.stream()
                .reduce(new byte[0], Bytes::concat);

            return ByteString.of(encoded);
        }
    }

}
