/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.camel.quarkus.component.vertx.websocket;

import java.net.URI;
import java.util.Optional;
import java.util.concurrent.ExecutionException;

import io.quarkus.arc.Arc;
import io.quarkus.runtime.LaunchMode;
import io.quarkus.runtime.RuntimeValue;
import io.quarkus.runtime.annotations.Recorder;
import io.quarkus.tls.TlsConfiguration;
import io.quarkus.tls.TlsConfigurationRegistry;
import io.quarkus.vertx.http.runtime.CertificateConfig;
import io.quarkus.vertx.http.runtime.ServerSslConfig;
import io.quarkus.vertx.http.runtime.VertxHttpConfig;
import io.vertx.core.Vertx;
import io.vertx.core.http.HttpClientOptions;
import io.vertx.core.http.WebSocketConnectOptions;
import io.vertx.ext.web.Router;
import org.apache.camel.CamelContext;
import org.apache.camel.component.vertx.websocket.VertxWebsocketComponent;
import org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration;
import org.apache.camel.component.vertx.websocket.VertxWebsocketConstants;
import org.apache.camel.component.vertx.websocket.VertxWebsocketEndpoint;
import org.apache.camel.component.vertx.websocket.VertxWebsocketHost;
import org.apache.camel.component.vertx.websocket.VertxWebsocketHostConfiguration;
import org.apache.camel.component.vertx.websocket.VertxWebsocketHostKey;
import org.apache.camel.spi.annotations.Component;
import org.apache.camel.util.ObjectHelper;

@Recorder
public class VertxWebsocketRecorder {
    private static volatile int PORT;
    private static volatile String HOST;

    private final RuntimeValue<VertxHttpConfig> httpConfigRuntimeValue;

    public VertxWebsocketRecorder(RuntimeValue<VertxHttpConfig> httpConfigRuntimeValue) {
        this.httpConfigRuntimeValue = httpConfigRuntimeValue;
    }

    public RuntimeValue<VertxWebsocketComponent> createVertxWebsocketComponent(
            RuntimeValue<Vertx> vertx,
            RuntimeValue<Router> router,
            LaunchMode launchMode) {

        VertxHttpConfig vertxHttpConfig = httpConfigRuntimeValue.getValue();
        boolean sslEnabled = isHttpSeverSecureTransportConfigured(vertxHttpConfig);
        int httpPort = vertxHttpConfig.determinePort(launchMode);
        int httpsPort = vertxHttpConfig.determineSslPort(launchMode);

        HOST = vertxHttpConfig.host();
        PORT = sslEnabled ? httpsPort : httpPort;

        QuarkusVertxWebsocketComponent component = new QuarkusVertxWebsocketComponent();
        component.setVertx(vertx.getValue());
        component.setRouter(router.getValue());
        component.setDefaultHost(HOST);
        component.setDefaultPort(PORT);
        return new RuntimeValue<>(component);
    }

    private boolean isHttpSeverSecureTransportConfigured(VertxHttpConfig httpConfig) {
        return httpServerTlsRegistryConfigurationExists(httpConfig) || httpServerLegacySslConfigurationExists(httpConfig);
    }

    private boolean httpServerTlsRegistryConfigurationExists(VertxHttpConfig httpConfig) {
        if (Arc.container() != null) {
            TlsConfigurationRegistry tlsConfigurationRegistry = Arc.container().select(TlsConfigurationRegistry.class).orNull();
            if (tlsConfigurationRegistry != null) {
                Optional<String> tlsConfigurationName = httpConfig.tlsConfigurationName();
                Optional<TlsConfiguration> defaultTlsConfiguration = tlsConfigurationRegistry.getDefault();
                if (tlsConfigurationName.isPresent() && tlsConfigurationRegistry.get(tlsConfigurationName.get()).isPresent()) {
                    return true;
                } else {
                    return defaultTlsConfiguration.isPresent() && defaultTlsConfiguration.get().getKeyStoreOptions() != null;
                }
            }
        }
        return false;
    }

    private boolean httpServerLegacySslConfigurationExists(VertxHttpConfig httpConfig) {
        ServerSslConfig ssl = httpConfig.ssl();
        if (ssl != null) {
            CertificateConfig certificate = ssl.certificate();
            if (certificate != null) {
                if (certificate.files().isPresent() && certificate.keyFiles().isPresent()) {
                    return true;
                }
                return certificate.keyStoreFile().isPresent() && certificate.keyStorePassword().isPresent();
            }
        }
        return false;
    }

    @Component("vertx-websocket")
    static final class QuarkusVertxWebsocketComponent extends VertxWebsocketComponent {
        @Override
        protected VertxWebsocketHost createVertxWebsocketHost(VertxWebsocketHostConfiguration hostConfiguration,
                VertxWebsocketHostKey hostKey) {
            // If a host / port was specified on the consumer, it must be the same as what the Quarkus HTTP server is bound to
            if (!hostKey.getHost().equals(HOST) || hostKey.getPort() != PORT) {
                String message = String.format(
                        "Invalid host/port %s:%d. The host/port can only be configured as %s:%d",
                        hostKey.getHost(), hostKey.getPort(), HOST, PORT);
                throw new IllegalArgumentException(message);
            }
            return new QuarkusVertxWebsocketHost(getCamelContext(), hostConfiguration, hostKey);
        }

        @Override
        protected VertxWebsocketEndpoint createEndpointInstance(String uri, VertxWebsocketConfiguration configuration) {
            return new QuarkusVertxWebsocketEndpoint(uri, this, configuration);
        }
    }

    public static final class QuarkusVertxWebsocketEndpoint extends VertxWebsocketEndpoint {
        public QuarkusVertxWebsocketEndpoint(String uri, VertxWebsocketComponent component,
                VertxWebsocketConfiguration configuration) {
            super(uri, component, configuration);
        }

        @Override
        public WebSocketConnectOptions getWebSocketConnectOptions(HttpClientOptions options) {
            WebSocketConnectOptions connectOptions = super.getWebSocketConnectOptions(options);
            URI uri = URI.create(getEndpointUri().replaceFirst("wss?:/*", ""));
            if (ObjectHelper.isNotEmpty(uri.getHost()) && uri.getPort() == -1) {
                connectOptions.setPort(connectOptions.isSsl() ? VertxWebsocketConstants.DEFAULT_VERTX_CLIENT_WSS_PORT
                        : VertxWebsocketConstants.DEFAULT_VERTX_CLIENT_WS_PORT);
            }
            return connectOptions;
        }
    }

    static final class QuarkusVertxWebsocketHost extends VertxWebsocketHost {
        public QuarkusVertxWebsocketHost(CamelContext camelContext, VertxWebsocketHostConfiguration websocketHostConfiguration,
                VertxWebsocketHostKey key) {
            super(camelContext, websocketHostConfiguration, key);
        }

        @Override
        public void start() throws InterruptedException, ExecutionException {
            // Noop as quarkus-vertx-http handles the server lifecycle
        }

        @Override
        public int getPort() {
            return PORT;
        }

        @Override
        public boolean isManagedHost(String host) {
            return HOST.equals(host);
        }

        @Override
        public boolean isManagedPort(int port) {
            return port == getPort();
        }
    }
}
