package demo;

import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;

import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;

/** Variant 1: @EnableMultiFactorAuthentication on the whole application. */
@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("global")
class MfaGlobalFlowTest extends MfaTestSupport {

    @Autowired MockMvc mvc;
    @Autowired MfaTokenCatcher catcher;

    @BeforeAll
    static void clean() {
        new java.io.File("cap/10-global-flow.txt").delete();
    }

    private static String where(MockHttpServletResponse r) {
        return r.getStatus() + (r.getRedirectedUrl() != null ? " -> " + r.getRedirectedUrl() : "");
    }

    @Test
    void passwordAloneIsNotEnough() throws Exception {
        String f = "10-global-flow.txt";

        var anon = mvc.perform(get("/api/profile").accept("text/html")).andReturn().getResponse();
        say(f, "1  anonymous GET /api/profile        : " + where(anon));

        var login = mvc.perform(formLogin().user("alice").password("pw")).andReturn();
        MockHttpSession session = (MockHttpSession) login.getRequest().getSession();
        say(f, "2  POST /login (alice / password)    : " + where(login.getResponse()));
        say(f, "   authorities after password login  : " + authorities(session));

        var blocked = mvc.perform(get("/api/profile").session(session).accept("text/html")).andReturn().getResponse();
        say(f, "3  GET /api/profile, password only   : " + where(blocked));

        var gen = mvc.perform(post("/ott/generate").param("username", "alice").session(session).with(csrf()))
                .andReturn().getResponse();
        say(f, "4  POST /ott/generate                : " + where(gen));
        String token = catcher.last();
        say(f, "   token issued (length)             : " + token.length());

        var ott = mvc.perform(post("/login/ott").param("token", token).session(session).with(csrf())).andReturn();
        MockHttpSession after = (MockHttpSession) ott.getRequest().getSession();
        say(f, "5  POST /login/ott (the token)       : " + where(ott.getResponse()));
        say(f, "   authorities after token login     : " + authorities(after));

        var ok = mvc.perform(get("/api/profile").session(after).accept("text/html")).andReturn().getResponse();
        say(f, "6  GET /api/profile, both factors    : " + ok.getStatus() + " " + ok.getContentAsString());

        var replay = mvc.perform(post("/login/ott").param("token", token).session(after).with(csrf())).andReturn().getResponse();
        say(f, "7  replaying the same token          : " + where(replay));

        assertThat(blocked.getStatus()).isEqualTo(302);
        assertThat(ok.getStatus()).isEqualTo(200);
    }
}
