package study.reactivesecurity;

import java.net.*;
import java.net.http.*;
import java.time.Duration;
import java.util.Base64;
import org.junit.jupiter.api.Test;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort;
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 WebSessionTest {
    @LocalServerPort int port;
    @Test void sessionIdRotatesCsrfProtectsWritesAndLogoutClearsIdentity() throws Exception {
        var cookies=new CookieManager(null,CookiePolicy.ACCEPT_ALL);
        try(var client=HttpClient.newBuilder().cookieHandler(cookies).build()) {
            var csrf=send(client,"GET","/browser/csrf",null,false);
            assertEquals(200,csrf.statusCode());
            String before=cookies.getCookieStore().getCookies().stream().filter(c->c.getName().equals("SESSION")).findFirst().orElseThrow().getValue();
            assertEquals(401,send(client,"GET","/browser/who",null,false).statusCode());
            assertEquals(200,send(client,"GET","/browser/who",null,true).statusCode());
            String after=cookies.getCookieStore().getCookies().stream().filter(c->c.getName().equals("SESSION")).findFirst().orElseThrow().getValue();
            assertNotEquals(before,after,"saved authentication changes WebSession id");
            try(var replay=HttpClient.newHttpClient()) {
                var oldCookieRequest=HttpRequest.newBuilder(URI.create("http://127.0.0.1:"+port+"/browser/who"))
                    .timeout(Duration.ofSeconds(5)).header("Cookie","SESSION="+before).GET().build();
                int status=replay.send(oldCookieRequest,HttpResponse.BodyHandlers.ofString()).statusCode();
                assertEquals(401,status,"pre-authentication session id cannot recover the saved identity");
                System.out.println("U31OBSERVE websession oldSessionCookieStatus="+status);
            }
            assertEquals("session:alice",send(client,"GET","/browser/who",null,false).body());
            String token=send(client,"GET","/browser/csrf",null,false).body();
            assertEquals(403,send(client,"POST","/browser/write",null,false).statusCode());
            assertEquals(403,send(client,"POST","/browser/write","wrong",false).statusCode());
            assertEquals(200,send(client,"POST","/browser/write",token,false).statusCode());
            assertEquals(200,send(client,"POST","/browser/logout",token,false).statusCode());
            assertEquals(401,send(client,"GET","/browser/who",null,false).statusCode());
            assertEquals(200,send(client,"GET","/browser/who",null,true).statusCode());
            System.out.println("U31OBSERVE websession idRotated=true cookiePersisted=true missingAndWrongCsrfDenied=true validWrite=true logoutAuthenticationCleared=true recovered=true");
        }
    }
    HttpResponse<String> send(HttpClient client,String method,String path,String csrf,boolean login) throws Exception {
        var builder=HttpRequest.newBuilder(URI.create("http://127.0.0.1:"+port+path)).timeout(Duration.ofSeconds(5));
        if(csrf!=null) builder.header("X-CSRF-TOKEN",csrf);
        if(login) builder.header("Authorization","Basic "+Base64.getEncoder().encodeToString("alice:lab-password".getBytes()));
        var response=client.send(builder.method(method,HttpRequest.BodyPublishers.noBody()).build(),HttpResponse.BodyHandlers.ofString());
        System.out.println("U31OBSERVE websession method="+method+" path="+path+" login="+login+" csrf="+(csrf!=null)+" status="+response.statusCode());
        return response;
    }
}
