/**
 * Copyright © 2012-2025 by Data Virtuality GmbH
 */
package org.teiid.jboss;

import org.jboss.as.controller.OperationContext;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.as.controller.PathAddress;
import org.jboss.as.controller.PathElement;
import org.jboss.as.controller.descriptions.ModelDescriptionConstants;
import org.jboss.as.controller.persistence.SubsystemMarshallingContext;
import org.jboss.as.network.SocketBinding;
import org.jboss.dmr.ModelNode;
import org.jboss.msc.service.ServiceBuilder;
import org.jboss.msc.service.ServiceTarget;
import org.jboss.msc.service.StartContext;
import org.jboss.staxmapper.FormattingXMLStreamWriter;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.mockito.MockedConstruction;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.teiid.common.buffer.BufferManager;
import org.teiid.deployers.VDBRepository;
import org.teiid.dqp.internal.process.DQPCore;
import org.teiid.jdbc.FipsManager;
import org.teiid.net.TeiidURL;
import org.teiid.transport.SSLConfiguration;
import org.teiid.transport.SocketListener;

import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import java.io.StringWriter;
import java.net.InetSocketAddress;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicReference;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;

/**
 *
 *
 * @author Roman Pedchenko
 * @date 2025.10.01
 */
public class TestFipsAttributes {
    @ParameterizedTest
    @CsvSource({
            "'disabled','BCFIPS','<ssl mode=\"enabled\"/>'",
            "'strict','BCFIPS1','<ssl mode=\"enabled\" fips=\"strict\" fips-provider=\"BCFIPS1\"/>'"
    })
    public void testTeiidSubsystemParser(String fips, String fipsProvider, String expectedResult) throws Exception {
        final String result = runWriter(String.format("""
                {
                  "transport": {
                      "jdbc-ssl": {
                        "name": "jdbc-ssl",
                        "ssl-mode": "enabled",
                        "ssl-fips": "%s",
                        "ssl-fips-provider": "%s"
                      }
                  }
                }
                """, fips, fipsProvider));
        assertThat(result).contains(expectedResult);
    }

    private static String runWriter(String input) throws XMLStreamException {
        StringWriter stringWriter = new StringWriter();
        final FormattingXMLStreamWriter xmlWriter = new FormattingXMLStreamWriter(XMLOutputFactory.newInstance().createXMLStreamWriter(stringWriter));

        ModelNode node = ModelNode.fromJSONString(input);

        TeiidSubsystemParser.INSTANCE.writeContent(xmlWriter, new SubsystemMarshallingContext(node, xmlWriter));
        xmlWriter.flush();
        xmlWriter.close();
        return stringWriter.toString();
    }

    @ParameterizedTest
    @CsvSource({
            "'enabled','BCFIPS1',true",
            "'disabled','BCFIPS2',false",
            "'wrong','BCFIPS3',false",
            "null,'BCFIPS4',false",
            "null,null,false",
    })
    public void testTransportAdd(String fips, String fipsProvider, boolean isFips) throws Exception {
        final SSLConfiguration sslConf = baseTestTransportAdd(fips, fipsProvider);
        assertThat(sslConf.isFips()).isEqualTo(isFips);
        assertThat(sslConf.isFipsStrict()).isEqualTo(isFips);
        assertThat(sslConf.getFipsProvider()).isEqualTo(fipsProvider);
    }

    private static TransportService buildTransportService(String fipsMode, String fipsProvider) throws OperationFailedException {
        AtomicReference<TransportService> transportServiceHolder = new AtomicReference<>();
        @SuppressWarnings("rawtypes") final ServiceBuilder sb = mock(ServiceBuilder.class);
        final ServiceTarget st = mock(ServiceTarget.class);
        //noinspection deprecation
        when(st.addService(any(), any())).thenAnswer(invocation -> {
            transportServiceHolder.set(invocation.getArgument(1));
            return sb;
        });
        final OperationContext op = mock(OperationContext.class, Mockito.RETURNS_DEEP_STUBS);
        when(op.getServiceTarget()).thenReturn(st);
        when(op.resolveExpressions(any())).thenAnswer(invocation -> invocation.getArgument(0));

        ModelNode operation = new ModelNode();
        PathAddress addr = PathAddress.pathAddress(
                PathElement.pathElement(ModelDescriptionConstants.SUBSYSTEM, TeiidExtension.TEIID_SUBSYSTEM),
                PathElement.pathElement("transport", "jdbc-ssl")
        );
        operation.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode());

        operation.get("socket-binding").set("dv-jdbc-ssl");
        operation.get("authentication-security-domain").set("dv-security");

        operation.get("ssl-fips").set(fipsMode);
        operation.get("ssl-fips-provider").set(fipsProvider);

        TransportAdd.INSTANCE.performRuntime(op, operation, new ModelNode());
        return transportServiceHolder.get();
    }

    private static SSLConfiguration baseTestTransportAdd(String fipsMode, String fipsProvider) throws OperationFailedException {
        final TransportService ts = buildTransportService(fipsMode, fipsProvider);
        assertThat(ts).isNotNull();
        assertThat(ts.getSocketConfig()).isNotNull();
        final SSLConfiguration sslConf = ts.getSocketConfig().getSSLConfiguration();
        assertThat(sslConf).isNotNull();
        return sslConf;
    }

    @ParameterizedTest
    @CsvSource({
            "'enabled','BCFIPS','enabled'",
            "'disabled','BCFIPS2',",
            "'wrong','BCFIPS3',",
            "null,'BCFIPS4',",
            "null,null,",
            "'enabled',null,'enabled'",
    })
    public void testTransportService(String fips, String fipsProvider, String expectedFips) throws Exception {
        final TransportService ts = buildTransportService(fips, fipsProvider);
        ts.getVdbRepositoryInjector().setValue(() -> mock(VDBRepository.class));
        ts.getDqpInjector().setValue(() -> mock(DQPCore.class));
        final SocketBinding sb = mock(SocketBinding.class);
        when(sb.getSocketAddress()).thenReturn(mock(InetSocketAddress.class));
        ts.getSocketBindingInjector().setValue(() -> sb);
        ts.getBufferManagerInjector().setValue(() -> mock(BufferManager.class));
        final AtomicReference<Properties> propsHolder = new AtomicReference<>();
        try (MockedStatic<FipsManager> fm = mockStatic(FipsManager.class);
             MockedConstruction<SocketListener> ignored =
                     mockConstruction(SocketListener.class, (mock, context) -> {
                     })) {
            fm.when(() -> FipsManager.initializeFipsMode(any())).thenAnswer(invocation -> {
                propsHolder.set(invocation.getArgument(0));
                return null;
            });
            ts.start(mock(StartContext.class));
            final Properties props = propsHolder.get();
            if (expectedFips != null) {
                assertThat(props).isNotNull();
                assertThat(props.get(TeiidURL.CONNECTION.FIPS)).isEqualTo(expectedFips);
                assertThat(props.get(TeiidURL.CONNECTION.FIPS_PROVIDER)).isEqualTo(fipsProvider);
            } else
                assertThat(props).isNull();
        }
    }
}
