/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * license agreements; and to You under the Apache License, version 2.0:
 *
 *   https://www.apache.org/licenses/LICENSE-2.0
 *
 * This file is part of the Apache Pekko project, which was derived from Akka.
 */

/*
 * Copyright (C) 2018-2023 Lightbend Inc. <https://www.lightbend.com>
 */

// #full-client
package example.myapp.helloworld;

import org.apache.pekko.actor.ActorSystem;
import org.apache.pekko.grpc.GrpcClientSettings;
import org.apache.pekko.pki.pem.DERPrivateKeyLoader;
import org.apache.pekko.pki.pem.PEMDecoder;
import example.myapp.helloworld.grpc.GreeterServiceClient;
import example.myapp.helloworld.grpc.HelloReply;
import example.myapp.helloworld.grpc.HelloRequest;

import javax.net.ssl.*;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.util.concurrent.CompletionStage;
import java.util.stream.Collectors;

public class MtlsGreeterClient {

  public static void main(String[] args) {
    ActorSystem system = ActorSystem.create("MtlsHelloWorldClient");

    GrpcClientSettings clientSettings =
        GrpcClientSettings.connectToServiceAt("localhost", 8443, system)
            .withSslContext(sslContext());

    GreeterServiceClient client = GreeterServiceClient.create(clientSettings, system);

    CompletionStage<HelloReply> reply =
        client.sayHello(HelloRequest.newBuilder().setName("Jonas").build());

    reply.whenComplete(
        (response, error) -> {
          if (error == null) {
            System.out.println("Successful reply: " + response);
          } else {
            System.out.println("Request failed");
            error.printStackTrace();
          }
          system.terminate();
        });
  }

  private static SSLContext sslContext() {
    try {
      PrivateKey clientPrivateKey =
          DERPrivateKeyLoader.load(PEMDecoder.decode(classPathFileAsString("/certs/client1.key")));
      CertificateFactory certFactory = CertificateFactory.getInstance("X.509");

      // keyStore is for the client cert and private key
      KeyStore keyStore = KeyStore.getInstance("PKCS12");
      keyStore.load(null);
      Certificate clientCertificate =
          certFactory.generateCertificate(
              MtlsGreeterClient.class.getResourceAsStream("/certs/client1.crt"));
      keyStore.setKeyEntry(
          "private",
          clientPrivateKey,
          // No password for our private client key
          new char[0],
          new Certificate[] {clientCertificate});
      KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
      keyManagerFactory.init(keyStore, null);
      KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();

      // trustStore is for what server certs the client trust
      KeyStore trustStore = KeyStore.getInstance("PKCS12");
      trustStore.load(null);
      // accept any server cert signed by this CA
      trustStore.setEntry(
          "rootCA",
          new KeyStore.TrustedCertificateEntry(
              certFactory.generateCertificate(
                  MtlsGreeterClient.class.getResourceAsStream("/certs/rootCA.crt"))),
          null);
      TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
      tmf.init(trustStore);
      TrustManager[] trustManagers = tmf.getTrustManagers();

      SSLContext context = SSLContext.getInstance("TLS");
      context.init(keyManagers, trustManagers, new SecureRandom());
      return context;
    } catch (Exception ex) {
      throw new RuntimeException("Failed to set up SSL context for the client", ex);
    }
  }

  private static String classPathFileAsString(String path) {
    try (InputStream inputStream = MtlsGreeterClient.class.getResourceAsStream(path)) {
      if (inputStream == null)
        throw new IllegalArgumentException("'" + path + "' is not present on the classpath");
      return new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))
          .lines()
          .collect(Collectors.joining("\n"));
    } catch (Exception ex) {
      throw new RuntimeException("Failed reading server key from classpath", ex);
    }
  }
}
// #full-client
