package study.reactivesecurity;

import java.time.Duration;
import org.junit.jupiter.api.Test;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler;
import org.springframework.messaging.rsocket.RSocketRequester;
import org.springframework.messaging.rsocket.RSocketStrategies;
import org.springframework.security.rsocket.core.SecuritySocketAcceptorInterceptor;
import org.springframework.security.rsocket.metadata.UsernamePasswordMetadata;
import org.springframework.security.rsocket.metadata.SimpleAuthenticationEncoder;
import org.springframework.util.MimeTypeUtils;
import io.rsocket.core.RSocketServer;
import io.rsocket.transport.netty.server.TcpServerTransport;
import io.rsocket.transport.netty.server.CloseableChannel;
import static org.junit.jupiter.api.Assertions.*;

@DirtiesContext(classMode=DirtiesContext.ClassMode.AFTER_CLASS)
@SpringBootTest(classes=SecurityLab.class,webEnvironment=SpringBootTest.WebEnvironment.RANDOM_PORT,
    properties={"server.address=127.0.0.1","spring.main.web-application-type=reactive"})
class RSocketAuthenticationTest {
    @Autowired RSocketMessageHandler handler;
    @Autowired SecuritySocketAcceptorInterceptor security;
    @Test void requestsAuthenticateAndAuthorizeIndependentlyOnOneRealConnection() {
        var strategies=RSocketStrategies.builder().encoder(new SimpleAuthenticationEncoder()).build();
        CloseableChannel listener=RSocketServer.create(handler.responder())
            .interceptors(registry->registry.forSocketAcceptor(security))
            .bindNow(TcpServerTransport.create("127.0.0.1",0));
        var requester=RSocketRequester.builder().rsocketStrategies(strategies).tcp("127.0.0.1",listener.address().getPort());
        try {
            assertThrows(io.rsocket.exceptions.ApplicationErrorException.class,()->call(requester,"read",null,null));
            assertThrows(io.rsocket.exceptions.ApplicationErrorException.class,()->call(requester,"read","alice","wrong"));
            String value=call(requester,"read","alice","lab-password");
            assertEquals("rsocket:alice",value);
            System.out.println("U31OBSERVE rsocket validUser=alice value="+value);
            assertThrows(io.rsocket.exceptions.ApplicationErrorException.class,()->call(requester,"admin","alice","lab-password"));
            assertEquals("rsocket-admin",call(requester,"admin","admin","lab-password"));
            assertEquals("rsocket:bob",call(requester,"read","bob","lab-password"));
            assertThrows(io.rsocket.exceptions.ApplicationErrorException.class,()->call(requester,"read",null,null));
            assertEquals("rsocket:alice",call(requester,"read","alice","lab-password"));
            System.out.println("U31OBSERVE rsocket missingAndWrongDenied=true roleDenied=true adminAllowed=true perRequestIdentity=true recovered=true");
        } finally {
            requester.dispose();listener.dispose();listener.onClose().block(Duration.ofSeconds(5));
            System.out.println("U31OBSERVE rsocket listenerDisposed="+listener.isDisposed());
        }
    }
    static String call(RSocketRequester requester,String route,String user,String password) {
        var request=requester.route(route);
        if(user!=null) request.metadata(new UsernamePasswordMetadata(user,password),
            MimeTypeUtils.parseMimeType("message/x.rsocket.authentication.v0"));
        return request.data("u31").retrieveMono(String.class).block(Duration.ofSeconds(5));
    }
}
