package com.airlock.iam.application.webserver.infrastructure.health;

import com.airlock.iam.application.parameters.domain.model.ApplicationParameters;
import com.airlock.iam.application.parameters.domain.model.WebAppParameters;
import com.airlock.iam.application.parameters.domain.model.WebServerParameters.ConnectorParameters;
import com.airlock.iam.elog.api.EDefaultLogType;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.health.infrastructure.HealthCheckWireFormat;
import com.airlock.iam.security.ssl.AllTrusting;
import com.airlock.iam.system.module.IamModule;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SNIHostName;
import javax.net.ssl.SSLParameters;
import org.eclipse.microprofile.health.HealthCheckResponse;
import org.eclipse.microprofile.health.HealthCheckResponse.Status;

@SuppressFBWarnings(value = "URLCONNECTION_SSRF_FD", justification = "URL is not constructed from untrusted input")
public final class ReadinessService {
   private static final ELogger LOG = ELoggerFactory.getLogger(ReadinessService.class);
   private static final Duration CONNECT_TIMEOUT = Duration.ofSeconds(10L);
   private static final Duration READ_TIMEOUT = Duration.ofSeconds(20L);
   private static final List<IamModule> HEALTH_CHECK_MODULES = List.of(
      IamModule.ADMINAPP, IamModule.LOGINAPP, IamModule.TRANSACTION_APPROVAL, IamModule.API_POLICY_SERVICE
   );
   private final ApplicationParameters parameters;

   public ReadinessService(ApplicationParameters parameters) {
      this.parameters = parameters;
      SSLParameters sslParameters = new SSLParameters();
      sslParameters.setServerNames(List.of(new SNIHostName("localhost")));
      sslParameters.setSNIMatchers(List.of(SNIHostName.createSNIMatcher(".*")));
   }

   public HealthCheckWireFormat checkAll() {
      List<HealthCheckResponse> checks = this.parameters
         .webAppParameters()
         .filter(webapp -> HEALTH_CHECK_MODULES.contains(webapp.module()))
         .map(this::response)
         .toList();
      return new HealthCheckWireFormat(checks);
   }

   private HealthCheckResponse response(WebAppParameters webapp) {
      return this.callReadinessEndpoint(webapp)
         .map(
            moduleWireFormat -> new HealthCheckResponse(
               webapp.module().identifier(), moduleWireFormat.getStatus(), Optional.of(responseDataOf(moduleWireFormat))
            )
         )
         .orElseGet(() -> new HealthCheckResponse(webapp.module().identifier(), Status.DOWN, Optional.empty()));
   }

   private static Map<String, Object> responseDataOf(HealthCheckWireFormat moduleWireFormat) {
      return moduleWireFormat.getChecks().stream().collect(Collectors.toMap(HealthCheckResponse::getName, HealthCheckResponse::getStatus));
   }

   private Optional<HealthCheckWireFormat> callReadinessEndpoint(WebAppParameters webApp) {
      ConnectorParameters connector = (ConnectorParameters)this.parameters
         .webServer()
         .httpConnector()
         .or(this.parameters.webServer()::httpsConnector)
         .orElseThrow(() -> new RuntimeException("No HTTP or HTTPS connector in parameters"));
      URI uri = readinessUri(connector, webApp);

      try {
         HttpURLConnection connection = (HttpURLConnection)uri.toURL().openConnection();
         connection.setConnectTimeout((int)CONNECT_TIMEOUT.toMillis());
         connection.setReadTimeout((int)READ_TIMEOUT.toMillis());
         if (connection instanceof HttpsURLConnection) {
            ((HttpsURLConnection)connection).setSSLSocketFactory(AllTrusting.allTrustingSslContext().getSocketFactory());
            ((HttpsURLConnection)connection).setHostnameVerifier(AllTrusting.allTrustingHostnameVerifier());
         }

         return HealthCheckWireFormat.fromJson(connection.getInputStream());
      } catch (IOException e) {
         LOG.warn(EDefaultLogType.GENERIC_LOG_TYPE, e.getMessage(), e);
         return Optional.empty();
      }
   }

   private static URI readinessUri(ConnectorParameters connector, WebAppParameters webApp) {
      try {
         return new URI(connector.scheme(), null, "localhost", connector.port(), readinessPath(webApp), null, null);
      } catch (URISyntaxException e) {
         throw new IllegalStateException(e);
      }
   }

   private static String readinessPath(WebAppParameters webApp) {
      return webApp.urlPath() + "/rest/health/ready";
   }
}
