package com.infoepoch.pms.agent.controller;

import com.infoepoch.pms.agent.platform.shared.exception.BusinessException;
import com.infoepoch.pms.agent.platform.shared.exception.GlobalExceptionHandler;
import com.infoepoch.pms.agent.platform.shared.response.ResultCode;
import com.infoepoch.pms.agent.properties.SsoProperties;
import com.infoepoch.pms.agent.service.auth.ExternalSsoTokenVerifier;
import com.infoepoch.pms.agent.service.auth.LoginSessionService;
import com.infoepoch.pms.agent.service.auth.TargetUserService;
import com.infoepoch.pms.agent.service.auth.dto.ExternalSsoUser;
import com.infoepoch.pms.agent.service.auth.dto.LoginSessionUser;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import static org.mockito.Mockito.doAnswer;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@ExtendWith(MockitoExtension.class)
class ExternalSsoControllerTest {

    @Mock
    private ExternalSsoTokenVerifier tokenVerifier;
    @Mock
    private TargetUserService targetUserService;
    @Mock
    private LoginSessionService loginSessionService;

    private MockMvc mockMvc;

    @BeforeEach
    void setUp() {
        mockMvc = MockMvcBuilders.standaloneSetup(new ExternalSsoController(
                        tokenVerifier,
                        targetUserService,
                        loginSessionService,
                        ssoProperties()))
                .setControllerAdvice(new GlobalExceptionHandler())
                .build();
    }

    @Test
    void failsWhenSsoCookieIsMissing() throws Exception {
        mockMvc.perform(get("/auth/external-sso"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.success").value(false))
                .andExpect(jsonPath("$.message").value("单点登录失败，缺少 pms_agent_sso_token"));
    }

    @Test
    void failsWhenUserCannotBeMatched() throws Exception {
        when(loginSessionService.getCookieValue(any(jakarta.servlet.http.HttpServletRequest.class), eq("pms_agent_sso_token")))
                .thenReturn("token-1");
        when(tokenVerifier.verify("token-1")).thenReturn(new ExternalSsoUser("union-js", "zhangsan", "token-1"));
        when(targetUserService.findLoginSessionUser("zhangsan"))
                .thenThrow(new BusinessException(ResultCode.PARAM_ERROR, "单点登录失败，目标系统不存在该用户"));

        mockMvc.perform(get("/auth/external-sso").header("Cookie", "pms_agent_sso_token=token-1"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.success").value(false))
                .andExpect(jsonPath("$.message").value("单点登录失败，目标系统不存在该用户"));
    }

    @Test
    void createsSessionClearsSsoCookieAndRedirects() throws Exception {
        ExternalSsoUser ssoUser = new ExternalSsoUser("union-js", "zhangsan", "token-1");
        LoginSessionUser loginUser = new LoginSessionUser("user-1", "zhangsan", "张三", "org-1", "组织一");
        when(loginSessionService.getCookieValue(any(jakarta.servlet.http.HttpServletRequest.class), eq("pms_agent_sso_token")))
                .thenReturn("token-1");
        when(tokenVerifier.verify("token-1")).thenReturn(ssoUser);
        when(targetUserService.findLoginSessionUser("zhangsan")).thenReturn(loginUser);
        doAnswer(invocation -> {
            invocation.getArgument(1, jakarta.servlet.http.HttpServletResponse.class)
                    .addHeader("Set-Cookie", "pms_agent_session=session-1; Path=/; Max-Age=7200; HttpOnly; SameSite=Lax");
            return null;
        }).when(loginSessionService).createLoginSession(eq(loginUser), any(jakarta.servlet.http.HttpServletResponse.class));
        doAnswer(invocation -> {
            invocation.getArgument(0, jakarta.servlet.http.HttpServletResponse.class)
                    .addHeader("Set-Cookie", "pms_agent_sso_token=; Path=/; Max-Age=0; HttpOnly; SameSite=Lax");
            return null;
        }).when(loginSessionService).clearSsoCookie(any(jakarta.servlet.http.HttpServletResponse.class));

        mockMvc.perform(get("/auth/external-sso")
                        .param("redirect", "/pms-agent/chat")
                        .header("Cookie", "pms_agent_sso_token=token-1"))
                .andExpect(status().isFound())
                .andExpect(redirectedUrl("/pms-agent/chat"))
                .andExpect(result -> {
                    String cookies = String.join("\n", result.getResponse().getHeaders("Set-Cookie"));
                    org.assertj.core.api.Assertions.assertThat(cookies)
                            .contains("pms_agent_session=session-1")
                            .contains("Path=/")
                            .contains("Max-Age=7200")
                            .contains("pms_agent_sso_token=")
                            .contains("Max-Age=0")
                            .contains("HttpOnly")
                            .contains("SameSite=Lax");
                });

        verify(loginSessionService).createLoginSession(eq(loginUser), any(jakarta.servlet.http.HttpServletResponse.class));
        verify(loginSessionService).clearSsoCookie(any(jakarta.servlet.http.HttpServletResponse.class));
    }

    @Test
    void fallsBackWhenRedirectIsUnsafe() throws Exception {
        ExternalSsoUser ssoUser = new ExternalSsoUser("union-js", "zhangsan", "token-1");
        LoginSessionUser loginUser = new LoginSessionUser("user-1", "zhangsan", "张三", "org-1", "组织一");
        when(loginSessionService.getCookieValue(any(jakarta.servlet.http.HttpServletRequest.class), eq("pms_agent_sso_token")))
                .thenReturn("token-1");
        when(tokenVerifier.verify("token-1")).thenReturn(ssoUser);
        when(targetUserService.findLoginSessionUser("zhangsan")).thenReturn(loginUser);

        mockMvc.perform(get("/auth/external-sso")
                        .param("redirect", "//evil.example")
                        .header("Cookie", "pms_agent_sso_token=token-1"))
                .andExpect(status().isFound())
                .andExpect(redirectedUrl("/pms-agent/"));
    }

    @Test
    void logoutClearsLoginSession() throws Exception {
        mockMvc.perform(post("/auth/logout").header("Cookie", "pms_agent_session=session-1"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.success").value(true));

        verify(loginSessionService).clearLoginSession(any(jakarta.servlet.http.HttpServletRequest.class),
                any(jakarta.servlet.http.HttpServletResponse.class));
    }

    private SsoProperties ssoProperties() {
        SsoProperties properties = new SsoProperties();
        properties.setCookieName("pms_agent_sso_token");
        return properties;
    }

}
