package com.xly.erp.module.usr.controller;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xly.erp.module.usr.security.LoginAttemptStore;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.test.context.ActiveProfiles;

import java.util.HashMap;
import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("test")
class AuthControllerIT {

    @Autowired
    private TestRestTemplate rest;

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private BCryptPasswordEncoder encoder;

    @Autowired
    private LoginAttemptStore loginAttemptStore;

    @LocalServerPort
    private int port;

    @BeforeEach
    @AfterEach
    void cleanup() {
        jdbcTemplate.update("DELETE FROM tUser WHERE sUserNo LIKE 'sp_test_%'");
        loginAttemptStore.clearFailures("sp_test_login_user");
        loginAttemptStore.clearFailures("sp_test_login_lock");
    }

    @Test
    void loginWithValidCredentials_returns200_withTokens() throws Exception {
        insertActiveUser("sp_test_login_user", "登录用户1", encoder.encode("666666"), false);

        Map<String, Object> body = new HashMap<>();
        body.put("sUserName", "登录用户1");
        body.put("password", "666666");
        body.put("version", "标准版");

        ResponseEntity<String> resp = rest.exchange(
                url(), HttpMethod.POST, new HttpEntity<>(body, jsonHeaders()), String.class);

        JsonNode jb = objectMapper.readTree(resp.getBody());
        assertThat(jb.get("code").asInt()).isZero();
        JsonNode data = jb.get("data");
        assertThat(data.get("accessToken").asText()).isNotBlank();
        assertThat(data.get("refreshToken").asText()).isNotBlank();
        assertThat(data.get("expiresIn").asInt()).isEqualTo(28800);
        assertThat(data.get("user").get("sUserNo").asText()).isEqualTo("sp_test_login_user");
        assertThat(data.get("user").has("sPasswordHash")).isFalse();
    }

    @Test
    void loginWithEmptyBody_returns40001() throws Exception {
        ResponseEntity<String> resp = rest.exchange(
                url(), HttpMethod.POST, new HttpEntity<>("{}", jsonHeaders()), String.class);
        assertThat(objectMapper.readTree(resp.getBody()).get("code").asInt()).isEqualTo(40001);
    }

    @Test
    void loginWithUserNotFound_returns40101() throws Exception {
        Map<String, Object> body = new HashMap<>();
        body.put("sUserName", "幽灵用户");
        body.put("password", "x");
        body.put("version", "标准版");
        ResponseEntity<String> resp = rest.exchange(
                url(), HttpMethod.POST, new HttpEntity<>(body, jsonHeaders()), String.class);
        assertThat(objectMapper.readTree(resp.getBody()).get("code").asInt()).isEqualTo(40101);
    }

    @Test
    void loginWithWrongPassword_returns40101() throws Exception {
        insertActiveUser("sp_test_login_user", "登录用户1", encoder.encode("666666"), false);

        Map<String, Object> body = new HashMap<>();
        body.put("sUserName", "登录用户1");
        body.put("password", "wrong");
        body.put("version", "标准版");

        ResponseEntity<String> resp = rest.exchange(
                url(), HttpMethod.POST, new HttpEntity<>(body, jsonHeaders()), String.class);
        assertThat(objectMapper.readTree(resp.getBody()).get("code").asInt()).isEqualTo(40101);
    }

    @Test
    void loginAfter5WrongPasswords_returns42301() throws Exception {
        insertActiveUser("sp_test_login_lock", "锁定用户", encoder.encode("666666"), false);

        Map<String, Object> body = new HashMap<>();
        body.put("sUserName", "锁定用户");
        body.put("password", "wrong");
        body.put("version", "标准版");

        for (int i = 0; i < LoginAttemptStore.MAX_ATTEMPTS - 1; i++) {
            rest.exchange(url(), HttpMethod.POST, new HttpEntity<>(body, jsonHeaders()), String.class);
        }
        ResponseEntity<String> resp = rest.exchange(
                url(), HttpMethod.POST, new HttpEntity<>(body, jsonHeaders()), String.class);
        assertThat(objectMapper.readTree(resp.getBody()).get("code").asInt()).isEqualTo(42301);

        loginAttemptStore.clearFailures("锁定用户");
    }

    private void insertActiveUser(String userNo, String userName, String passwordHash, boolean deleted) {
        jdbcTemplate.update(
                "INSERT INTO tUser (sBrandsId, sSubsidiaryId, tCreateDate, sUserNo, sUserName, "
                        + "sUserType, sLanguage, bCanModifyDocs, sPasswordHash, sCreatedBy, bDeleted) "
                        + "VALUES ('XLY','XLY', NOW(), ?, ?, '普通用户', 'zh', 0, ?, 'STUB_ADMIN', ?)",
                userNo, userName, passwordHash, deleted ? 1 : 0);
    }

    private static HttpHeaders jsonHeaders() {
        HttpHeaders h = new HttpHeaders();
        h.setContentType(MediaType.APPLICATION_JSON);
        return h;
    }

    private String url() {
        return "http://localhost:" + port + "/api/usr/auth/login";
    }
}
