package app.alextran.immich

import android.annotation.SuppressLint
import android.content.Context
import app.alextran.immich.core.SSLConfig
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import java.io.ByteArrayInputStream
import java.net.InetSocketAddress
import java.net.Socket
import java.security.KeyStore
import java.security.cert.X509Certificate
import javax.net.ssl.HostnameVerifier
import javax.net.ssl.HttpsURLConnection
import javax.net.ssl.KeyManager
import javax.net.ssl.KeyManagerFactory
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLEngine
import javax.net.ssl.SSLSession
import javax.net.ssl.TrustManager
import javax.net.ssl.TrustManagerFactory
import javax.net.ssl.X509ExtendedTrustManager

/**
 * Android plugin for Dart `HttpSSLOptions`
 */
class HttpSSLOptionsPlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
  private var methodChannel: MethodChannel? = null

  override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
    onAttachedToEngine(binding.applicationContext, binding.binaryMessenger)
  }

  private fun onAttachedToEngine(ctx: Context, messenger: BinaryMessenger) {
    methodChannel = MethodChannel(messenger, "immich/httpSSLOptions")
    methodChannel?.setMethodCallHandler(this)
  }

  override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
    onDetachedFromEngine()
  }

  private fun onDetachedFromEngine() {
    methodChannel?.setMethodCallHandler(null)
    methodChannel = null
  }

  override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
    try {
      when (call.method) {
        "apply" -> {
          val args = call.arguments<ArrayList<*>>()!!
          val allowSelfSigned = args[0] as Boolean
          val serverHost = args[1] as? String
          val clientCertHash = (args[2] as? ByteArray)

          var tm: Array<TrustManager>? = null
          if (allowSelfSigned) {
            tm = arrayOf(AllowSelfSignedTrustManager(serverHost))
          }

          var km: Array<KeyManager>? = null
          if (clientCertHash != null) {
            val cert = ByteArrayInputStream(clientCertHash)
            val password = (args[3] as String).toCharArray()
            val keyStore = KeyStore.getInstance("PKCS12")
            keyStore.load(cert, password)
            val keyManagerFactory =
              KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
            keyManagerFactory.init(keyStore, null)
            km = keyManagerFactory.keyManagers
          }

          // Update shared SSL config for OkHttp and other HTTP clients
          SSLConfig.apply(km, tm, allowSelfSigned, serverHost, clientCertHash?.contentHashCode() ?: 0)

          val sslContext = SSLContext.getInstance("TLS")
          sslContext.init(km, tm, null)
          HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.socketFactory)

          HttpsURLConnection.setDefaultHostnameVerifier(AllowSelfSignedHostnameVerifier(args[1] as? String))

          result.success(true)
        }

        else -> result.notImplemented()
      }
    } catch (e: Throwable) {
      result.error("error", e.message, null)
    }
  }

  @SuppressLint("CustomX509TrustManager")
  class AllowSelfSignedTrustManager(private val serverHost: String?) : X509ExtendedTrustManager() {
    private val defaultTrustManager: X509ExtendedTrustManager = getDefaultTrustManager()

    override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?) =
      defaultTrustManager.checkClientTrusted(chain, authType)

    override fun checkClientTrusted(
      chain: Array<out X509Certificate>?, authType: String?, socket: Socket?
    ) = defaultTrustManager.checkClientTrusted(chain, authType, socket)

    override fun checkClientTrusted(
      chain: Array<out X509Certificate>?, authType: String?, engine: SSLEngine?
    ) = defaultTrustManager.checkClientTrusted(chain, authType, engine)

    override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) {
      if (serverHost == null) return
      defaultTrustManager.checkServerTrusted(chain, authType)
    }

    override fun checkServerTrusted(
      chain: Array<out X509Certificate>?, authType: String?, socket: Socket?
    ) {
      if (serverHost == null) return
      val socketAddress = socket?.remoteSocketAddress
      if (socketAddress is InetSocketAddress && socketAddress.hostName == serverHost) return
      defaultTrustManager.checkServerTrusted(chain, authType, socket)
    }

    override fun checkServerTrusted(
      chain: Array<out X509Certificate>?, authType: String?, engine: SSLEngine?
    ) {
      if (serverHost == null || engine?.peerHost == serverHost) return
      defaultTrustManager.checkServerTrusted(chain, authType, engine)
    }

    override fun getAcceptedIssuers(): Array<X509Certificate> = defaultTrustManager.acceptedIssuers

    private fun getDefaultTrustManager(): X509ExtendedTrustManager {
      val factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
      factory.init(null as KeyStore?)
      return factory.trustManagers.filterIsInstance<X509ExtendedTrustManager>().first()
    }
  }

  class AllowSelfSignedHostnameVerifier(private val serverHost: String?) : HostnameVerifier {
    companion object {
      private val _defaultHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier()
    }

    override fun verify(hostname: String?, session: SSLSession?): Boolean {
      if (serverHost == null || hostname == serverHost) {
        return true
      } else {
        return _defaultHostnameVerifier.verify(hostname, session)
      }
    }
  }
}
