/*
 * 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) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
 */

package org.apache.pekko.remote.transport.netty

import java.net.SocketAddress

import scala.annotation.nowarn
import scala.jdk.CollectionConverters._

import io.netty.channel.Channel
import io.netty.handler.ssl.SslHandler
import io.netty.util.concurrent.Future

import com.typesafe.config.Config
import org.apache.pekko
import pekko.event.MarkerLoggingAdapter

/**
 * INTERNAL API
 */
private[pekko] class SSLSettings(config: Config) {

  import config.getBoolean
  import config.getString
  import config.getStringList

  val SSLKeyStore = getString("key-store")
  val SSLTrustStore = getString("trust-store")
  val SSLKeyStorePassword = getString("key-store-password")
  val SSLKeyPassword = getString("key-password")

  val SSLTrustStorePassword = getString("trust-store-password")

  val SSLEnabledAlgorithms = getStringList("enabled-algorithms").asScala.toSet

  val SSLProtocol = getString("protocol")

  val SSLRandomNumberGenerator = getString("random-number-generator")

  val SSLRequireMutualAuthentication = getBoolean("require-mutual-authentication")

  val SSLHostnameVerification: Boolean =
    if (config.hasPath("hostname-verification")) config.getBoolean("hostname-verification")
    else false
}

/**
 * INTERNAL API
 *
 * Used for adding SSL support to Netty pipeline.
 * The `SSLEngine` is created via the configured [[SSLEngineProvider]].
 */
@nowarn("msg=deprecated")
private[pekko] object NettySSLSupport {

  /**
   * Construct a SSLHandler which can be inserted into a Netty server/client pipeline
   */
  def apply(
      sslEngineProvider: SSLEngineProvider,
      isClient: Boolean,
      log: MarkerLoggingAdapter,
      hostname: Option[String] = None,
      port: Int = -1): SslHandler = {
    val sslEngine =
      if (isClient) {
        hostname match {
          case Some(h) => sslEngineProvider.createClientSSLEngine(h, port)
          case None    => sslEngineProvider.createClientSSLEngine()
        }
      } else sslEngineProvider.createServerSSLEngine()
    val handler = new SslHandler(sslEngine)
    handler.handshakeFuture().addListener((future: Future[Channel]) => {
      if (!future.isSuccess) {
        val channel = handler.closeOutbound().channel()
        log.warning(
          "TLS handshake failed for remote address [{}]: {}",
          formatRemoteAddress(channel.remoteAddress()),
          formatCause(future.cause()))
        channel.close()
      }
    })
    handler
  }

  private[netty] def formatRemoteAddress(remoteAddress: SocketAddress): String =
    Option(remoteAddress).map(_.toString).getOrElse("unknown")

  private[netty] def formatCause(cause: Throwable): String =
    Option(cause).flatMap(t => Option(t.getMessage).orElse(Some(t.toString))).getOrElse("unknown cause")
}
