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

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xly.erp.common.security.TestJwtHelper;
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.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 ModuleControllerIT {

    @Autowired
    private TestRestTemplate rest;

    @Autowired
    private TestJwtHelper testJwtHelper;

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    private ObjectMapper objectMapper;

    @LocalServerPort
    private int port;

    @BeforeEach
    @AfterEach
    void cleanup() {
        jdbcTemplate.update("DELETE FROM tModule WHERE sProcedureName LIKE 'sp_test_%'");
    }

    @Test
    void postValidBody_with_jwt_returns200_andPersists() throws Exception {
        String token = testJwtHelper.signFor("ADMIN001");
        HttpHeaders headers = jsonHeaders();
        headers.set("Authorization", "Bearer " + token);

        ResponseEntity<String> resp = rest.exchange(
                "http://localhost:" + port + "/api/mod/modules",
                HttpMethod.POST,
                new HttpEntity<>(validBody("sp_test_ctrl_ok", "正常路径"), headers),
                String.class);

        assertThat(resp.getStatusCode().value()).isEqualTo(200);
        JsonNode body = objectMapper.readTree(resp.getBody());
        assertThat(body.get("code").asInt()).isZero();
        int newId = body.get("data").get("iIncrement").asInt();
        assertThat(newId).isPositive();

        Map<String, Object> row = jdbcTemplate.queryForMap(
                "SELECT sProcedureName, sBrandsId, sCreatedBy FROM tModule WHERE iIncrement = ?", newId);
        assertThat(row.get("sProcedureName")).isEqualTo("sp_test_ctrl_ok");
        assertThat(row.get("sBrandsId")).isEqualTo("XLY");
        assertThat(row.get("sCreatedBy")).isEqualTo("ADMIN001");
    }

    @Test
    void postEmptyBody_returns40001_withFieldHint() throws Exception {
        String token = testJwtHelper.signFor("ADMIN001");
        HttpHeaders headers = jsonHeaders();
        headers.set("Authorization", "Bearer " + token);

        ResponseEntity<String> resp = rest.exchange(
                url(),
                HttpMethod.POST,
                new HttpEntity<>("{}", headers),
                String.class);

        JsonNode body = objectMapper.readTree(resp.getBody());
        assertThat(body.get("code").asInt()).isEqualTo(40001);
        assertThat(body.get("msg").asText()).containsAnyOf(
                "sProcedureName", "sDisplayType", "sModuleType", "sManageDeptEn", "sModuleNameZh");
    }

    @Test
    void postInvalidDisplayType_returns40010() throws Exception {
        String token = testJwtHelper.signFor("ADMIN001");
        HttpHeaders headers = jsonHeaders();
        headers.set("Authorization", "Bearer " + token);
        Map<String, Object> bad = validBody("sp_test_bad_type", "枚举非法");
        bad.put("sDisplayType", "火星");

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

        JsonNode body = objectMapper.readTree(resp.getBody());
        assertThat(body.get("code").asInt()).isEqualTo(40010);
    }

    @Test
    void postDuplicateProcedureName_returns40020() throws Exception {
        String token = testJwtHelper.signFor("ADMIN001");
        HttpHeaders headers = jsonHeaders();
        headers.set("Authorization", "Bearer " + token);
        Map<String, Object> first = validBody("sp_test_dup", "首次");
        ResponseEntity<String> r1 = rest.exchange(
                url(), HttpMethod.POST, new HttpEntity<>(first, headers), String.class);
        assertThat(objectMapper.readTree(r1.getBody()).get("code").asInt()).isZero();

        Map<String, Object> dup = validBody("sp_test_dup", "重复");
        ResponseEntity<String> r2 = rest.exchange(
                url(), HttpMethod.POST, new HttpEntity<>(dup, headers), String.class);
        JsonNode body = objectMapper.readTree(r2.getBody());
        assertThat(body.get("code").asInt()).isEqualTo(40020);
    }

    @Test
    void postWithMissingParent_returns40021() throws Exception {
        String token = testJwtHelper.signFor("ADMIN001");
        HttpHeaders headers = jsonHeaders();
        headers.set("Authorization", "Bearer " + token);
        Map<String, Object> orphan = validBody("sp_test_orphan", "缺父");
        orphan.put("iParentId", 99999999);

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

        JsonNode body = objectMapper.readTree(resp.getBody());
        assertThat(body.get("code").asInt()).isEqualTo(40021);
    }

    @Test
    void postWithoutJwt_returns20001() throws Exception {
        HttpHeaders headers = jsonHeaders();
        Map<String, Object> body = validBody("sp_test_nojwt", "无JWT");

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

        assertThat(objectMapper.readTree(resp.getBody()).get("code").asInt()).isEqualTo(20001);
        Integer count = jdbcTemplate.queryForObject(
                "SELECT COUNT(1) FROM tModule WHERE sProcedureName = 'sp_test_nojwt'", Integer.class);
        assertThat(count).isZero();
    }

    @Test
    void postWithTamperedJwt_returns20001() throws Exception {
        HttpHeaders headers = jsonHeaders();
        headers.set("Authorization", "Bearer not.a.real.jwt");
        Map<String, Object> body = validBody("sp_test_tampered", "伪JWT");

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

        JsonNode jb = objectMapper.readTree(resp.getBody());
        assertThat(jb.get("code").asInt()).isEqualTo(20001);
    }

    @Test
    void putValidBody_with_jwt_returns200_andUpdatesEditableFields() throws Exception {
        Integer id = insertOriginal("sp_test_put_orig", "原名", "ORIG_USER");
        String token = testJwtHelper.signFor("ADMIN001");
        HttpHeaders headers = jsonHeaders();
        headers.set("Authorization", "Bearer " + token);
        Map<String, Object> body = updateBody();
        body.put("sModuleNameZh", "新名");
        body.put("sDisplayType", "前端业务");

        ResponseEntity<String> resp = rest.exchange(
                idUrl(id), HttpMethod.PUT, new HttpEntity<>(body, headers), String.class);

        assertThat(resp.getStatusCode().value()).isEqualTo(200);
        JsonNode jb = objectMapper.readTree(resp.getBody());
        assertThat(jb.get("code").asInt()).isZero();
        assertThat(jb.get("data").get("iIncrement").asInt()).isEqualTo(id);

        Map<String, Object> row = jdbcTemplate.queryForMap(
                "SELECT sModuleNameZh, sDisplayType, sProcedureName, sCreatedBy FROM tModule WHERE iIncrement = ?", id);
        assertThat(row.get("sModuleNameZh")).isEqualTo("新名");
        assertThat(row.get("sDisplayType")).isEqualTo("前端业务");
        assertThat(row.get("sProcedureName")).isEqualTo("sp_test_put_orig");
        assertThat(row.get("sCreatedBy")).isEqualTo("ORIG_USER");
    }

    @Test
    void putNonExistentId_returns40400() throws Exception {
        String token = testJwtHelper.signFor("ADMIN001");
        HttpHeaders headers = jsonHeaders();
        headers.set("Authorization", "Bearer " + token);

        ResponseEntity<String> resp = rest.exchange(
                idUrl(99999998), HttpMethod.PUT, new HttpEntity<>(updateBody(), headers), String.class);

        JsonNode jb = objectMapper.readTree(resp.getBody());
        assertThat(jb.get("code").asInt()).isEqualTo(40400);
    }

    @Test
    void putInvalidDisplayType_returns40010() throws Exception {
        Integer id = insertOriginal("sp_test_put_invalidtype", "原", "ORIG");
        String token = testJwtHelper.signFor("ADMIN001");
        HttpHeaders headers = jsonHeaders();
        headers.set("Authorization", "Bearer " + token);
        Map<String, Object> body = updateBody();
        body.put("sDisplayType", "火星");

        ResponseEntity<String> resp = rest.exchange(
                idUrl(id), HttpMethod.PUT, new HttpEntity<>(body, headers), String.class);

        assertThat(objectMapper.readTree(resp.getBody()).get("code").asInt()).isEqualTo(40010);
    }

    @Test
    void putSelfParent_returns40021() throws Exception {
        Integer id = insertOriginal("sp_test_put_self", "原", "ORIG");
        String token = testJwtHelper.signFor("ADMIN001");
        HttpHeaders headers = jsonHeaders();
        headers.set("Authorization", "Bearer " + token);
        Map<String, Object> body = updateBody();
        body.put("iParentId", id);

        ResponseEntity<String> resp = rest.exchange(
                idUrl(id), HttpMethod.PUT, new HttpEntity<>(body, headers), String.class);

        assertThat(objectMapper.readTree(resp.getBody()).get("code").asInt()).isEqualTo(40021);
    }

    @Test
    void putCyclicParent_returns40021() throws Exception {
        Integer rootId = insertOriginal("sp_test_put_root", "根", "ORIG");
        jdbcTemplate.update(
                "INSERT INTO tModule (sBrandsId, sSubsidiaryId, tCreateDate, sDisplayType, sProcedureName, "
                        + "sModuleType, sManageDeptEn, bShowPermission, sModuleNameZh, iParentId, iSortOrder, sCreatedBy, bDeleted) "
                        + "VALUES ('XLY','XLY', NOW(), '手机端', 'sp_test_put_child', '业务模块', 'IT', 0, '子', ?, 0, 'ORIG', 0)",
                rootId);
        Integer childId = jdbcTemplate.queryForObject(
                "SELECT iIncrement FROM tModule WHERE sProcedureName = 'sp_test_put_child'", Integer.class);

        String token = testJwtHelper.signFor("ADMIN001");
        HttpHeaders headers = jsonHeaders();
        headers.set("Authorization", "Bearer " + token);
        Map<String, Object> body = updateBody();
        body.put("iParentId", childId);

        ResponseEntity<String> resp = rest.exchange(
                idUrl(rootId), HttpMethod.PUT, new HttpEntity<>(body, headers), String.class);

        assertThat(objectMapper.readTree(resp.getBody()).get("code").asInt()).isEqualTo(40021);
    }

    @Test
    void putWithoutJwt_returns20001() throws Exception {
        Integer id = insertOriginal("sp_test_put_nojwt", "原", "ORIG_USER");
        HttpHeaders headers = jsonHeaders();
        Map<String, Object> body = updateBody();
        body.put("sModuleNameZh", "更新无jwt");

        ResponseEntity<String> resp = rest.exchange(
                idUrl(id), HttpMethod.PUT, new HttpEntity<>(body, headers), String.class);

        assertThat(objectMapper.readTree(resp.getBody()).get("code").asInt()).isEqualTo(20001);
        String name = jdbcTemplate.queryForObject(
                "SELECT sModuleNameZh FROM tModule WHERE iIncrement = ?", String.class, id);
        assertThat(name).isEqualTo("原");
    }

    @Test
    void putTamperedJwt_returns20001() throws Exception {
        Integer id = insertOriginal("sp_test_put_tamper", "原", "ORIG");
        HttpHeaders headers = jsonHeaders();
        headers.set("Authorization", "Bearer not.a.real.jwt");

        ResponseEntity<String> resp = rest.exchange(
                idUrl(id), HttpMethod.PUT, new HttpEntity<>(updateBody(), headers), String.class);

        assertThat(objectMapper.readTree(resp.getBody()).get("code").asInt()).isEqualTo(20001);
    }

    @Test
    void deleteValidId_with_jwt_returns200_andSoftDeletes() throws Exception {
        Integer id = insertOriginal("sp_test_del_ok", "原", "ORIG_USER");
        String token = testJwtHelper.signFor("ADMIN001");
        HttpHeaders headers = jsonHeaders();
        headers.set("Authorization", "Bearer " + token);

        ResponseEntity<String> resp = rest.exchange(
                idUrl(id), HttpMethod.DELETE, new HttpEntity<>(headers), String.class);

        assertThat(resp.getStatusCode().value()).isEqualTo(200);
        JsonNode jb = objectMapper.readTree(resp.getBody());
        assertThat(jb.get("code").asInt()).isZero();
        assertThat(jb.get("data").isNull()).isTrue();

        Map<String, Object> row = jdbcTemplate.queryForMap(
                "SELECT bDeleted, sDeletedBy, tDeletedDate, sProcedureName, sCreatedBy FROM tModule WHERE iIncrement = ?", id);
        assertThat(row.get("bDeleted")).isEqualTo(true);
        assertThat(row.get("sDeletedBy")).isEqualTo("ADMIN001");
        assertThat(row.get("tDeletedDate")).isNotNull();
        assertThat(row.get("sProcedureName")).isEqualTo("sp_test_del_ok");
        assertThat(row.get("sCreatedBy")).isEqualTo("ORIG_USER");
    }

    @Test
    void deleteNonExistentId_returns40400() throws Exception {
        String token = testJwtHelper.signFor("ADMIN001");
        HttpHeaders headers = jsonHeaders();
        headers.set("Authorization", "Bearer " + token);

        ResponseEntity<String> resp = rest.exchange(
                idUrl(99999996), HttpMethod.DELETE, new HttpEntity<>(headers), String.class);

        assertThat(objectMapper.readTree(resp.getBody()).get("code").asInt()).isEqualTo(40400);
    }

    @Test
    void deleteAlreadyDeletedId_returns40400() throws Exception {
        Integer id = insertOriginal("sp_test_del_already", "已删", "ORIG");
        jdbcTemplate.update("UPDATE tModule SET bDeleted = 1 WHERE iIncrement = ?", id);
        String token = testJwtHelper.signFor("ADMIN001");
        HttpHeaders headers = jsonHeaders();
        headers.set("Authorization", "Bearer " + token);

        ResponseEntity<String> resp = rest.exchange(
                idUrl(id), HttpMethod.DELETE, new HttpEntity<>(headers), String.class);

        assertThat(objectMapper.readTree(resp.getBody()).get("code").asInt()).isEqualTo(40400);
    }

    @Test
    void deleteWithActiveChildren_returns40901() throws Exception {
        Integer rootId = insertOriginal("sp_test_del_root", "根", "ORIG");
        jdbcTemplate.update(
                "INSERT INTO tModule (sBrandsId, sSubsidiaryId, tCreateDate, sDisplayType, sProcedureName, "
                        + "sModuleType, sManageDeptEn, bShowPermission, sModuleNameZh, iParentId, iSortOrder, sCreatedBy, bDeleted) "
                        + "VALUES ('XLY','XLY', NOW(), '手机端', 'sp_test_del_child', '业务模块', 'IT', 0, '子', ?, 0, 'ORIG', 0)",
                rootId);
        String token = testJwtHelper.signFor("ADMIN001");
        HttpHeaders headers = jsonHeaders();
        headers.set("Authorization", "Bearer " + token);

        ResponseEntity<String> resp = rest.exchange(
                idUrl(rootId), HttpMethod.DELETE, new HttpEntity<>(headers), String.class);

        assertThat(objectMapper.readTree(resp.getBody()).get("code").asInt()).isEqualTo(40901);
        Boolean stillAlive = jdbcTemplate.queryForObject(
                "SELECT bDeleted FROM tModule WHERE iIncrement = ?", Boolean.class, rootId);
        assertThat(stillAlive).isFalse();
    }

    @Test
    void deleteWithoutJwt_returns20001() throws Exception {
        Integer id = insertOriginal("sp_test_del_nojwt", "原", "ORIG");
        HttpHeaders headers = jsonHeaders();

        ResponseEntity<String> resp = rest.exchange(
                idUrl(id), HttpMethod.DELETE, new HttpEntity<>(headers), String.class);

        assertThat(objectMapper.readTree(resp.getBody()).get("code").asInt()).isEqualTo(20001);
        Boolean stillAlive = jdbcTemplate.queryForObject(
                "SELECT bDeleted FROM tModule WHERE iIncrement = ?", Boolean.class, id);
        assertThat(stillAlive).isFalse();
    }

    @Test
    void deleteTamperedJwt_returns20001() throws Exception {
        Integer id = insertOriginal("sp_test_del_tamper", "原", "ORIG");
        HttpHeaders headers = jsonHeaders();
        headers.set("Authorization", "Bearer not.a.real.jwt");

        ResponseEntity<String> resp = rest.exchange(
                idUrl(id), HttpMethod.DELETE, new HttpEntity<>(headers), String.class);

        assertThat(objectMapper.readTree(resp.getBody()).get("code").asInt()).isEqualTo(20001);
        Boolean stillAlive = jdbcTemplate.queryForObject(
                "SELECT bDeleted FROM tModule WHERE iIncrement = ?", Boolean.class, id);
        assertThat(stillAlive).isFalse();
    }

    @Test
    void getEmptyKeyword_returnsCompleteTreeAsForest() throws Exception {
        Integer rootId = insertOriginal("sp_test_get_root", "查询用根", "ORIG");
        jdbcTemplate.update(
                "INSERT INTO tModule (sBrandsId, sSubsidiaryId, tCreateDate, sDisplayType, sProcedureName, "
                        + "sModuleType, sManageDeptEn, bShowPermission, sModuleNameZh, iParentId, iSortOrder, sCreatedBy, bDeleted) "
                        + "VALUES ('XLY','XLY', NOW(), '手机端', 'sp_test_get_child', '业务模块', 'IT', 0, '查询用子', ?, 0, 'ORIG', 0)",
                rootId);
        Integer childId = jdbcTemplate.queryForObject(
                "SELECT iIncrement FROM tModule WHERE sProcedureName = 'sp_test_get_child'", Integer.class);

        String token = testJwtHelper.signFor("ADMIN001");
        HttpHeaders headers = jsonHeaders();
        headers.set("Authorization", "Bearer " + token);
        ResponseEntity<String> resp = rest.exchange(
                listUrl(null), HttpMethod.GET, new HttpEntity<>(headers), String.class);

        JsonNode jb = objectMapper.readTree(resp.getBody());
        assertThat(jb.get("code").asInt()).isZero();
        JsonNode data = jb.get("data");
        assertThat(data.isArray()).isTrue();
        JsonNode rootNode = findById(data, rootId);
        assertThat(rootNode).isNotNull();
        JsonNode childNode = findById(rootNode.get("children"), childId);
        assertThat(childNode).isNotNull();
        assertThat(childNode.get("sModuleNameZh").asText()).isEqualTo("查询用子");
    }

    @Test
    void getKeywordMatch_returnsForest() throws Exception {
        insertOriginal("sp_test_get_kw_a", "系统模块A", "ORIG");
        insertOriginal("sp_test_get_kw_b", "用户模块B", "ORIG");
        String token = testJwtHelper.signFor("ADMIN001");
        HttpHeaders headers = jsonHeaders();
        headers.set("Authorization", "Bearer " + token);

        ResponseEntity<String> resp = rest.exchange(
                listUrl("系统"), HttpMethod.GET, new HttpEntity<>(headers), String.class);

        JsonNode jb = objectMapper.readTree(resp.getBody());
        assertThat(jb.get("code").asInt()).isZero();
        JsonNode data = jb.get("data");
        assertThat(data.isArray()).isTrue();
        for (JsonNode node : data) {
            assertThat(node.get("sModuleNameZh").asText()).contains("系统");
        }
    }

    @Test
    void getKeywordTooLong_returns40001() throws Exception {
        String token = testJwtHelper.signFor("ADMIN001");
        HttpHeaders headers = jsonHeaders();
        headers.set("Authorization", "Bearer " + token);

        ResponseEntity<String> resp = rest.exchange(
                listUrl("x".repeat(101)), HttpMethod.GET, new HttpEntity<>(headers), String.class);

        assertThat(objectMapper.readTree(resp.getBody()).get("code").asInt()).isEqualTo(40001);
    }

    @Test
    void getNoMatch_returnsEmptyArray() throws Exception {
        String token = testJwtHelper.signFor("ADMIN001");
        HttpHeaders headers = jsonHeaders();
        headers.set("Authorization", "Bearer " + token);

        ResponseEntity<String> resp = rest.exchange(
                listUrl("不存在的关键字XYZ-zzz"), HttpMethod.GET, new HttpEntity<>(headers), String.class);

        JsonNode data = objectMapper.readTree(resp.getBody()).get("data");
        assertThat(data.isArray()).isTrue();
        assertThat(data.size()).isZero();
    }

    @Test
    void getWithoutJwt_returns20001() throws Exception {
        HttpHeaders headers = jsonHeaders();
        ResponseEntity<String> resp = rest.exchange(
                listUrl(null), HttpMethod.GET, new HttpEntity<>(headers), String.class);
        assertThat(objectMapper.readTree(resp.getBody()).get("code").asInt()).isEqualTo(20001);
    }

    @Test
    void getTamperedJwt_returns20001() throws Exception {
        HttpHeaders headers = jsonHeaders();
        headers.set("Authorization", "Bearer not.a.real.jwt");
        ResponseEntity<String> resp = rest.exchange(
                listUrl(null), HttpMethod.GET, new HttpEntity<>(headers), String.class);
        assertThat(objectMapper.readTree(resp.getBody()).get("code").asInt()).isEqualTo(20001);
    }

    private String listUrl(String keyword) {
        String base = "http://localhost:" + port + "/api/mod/modules";
        return keyword == null ? base : base + "?keyword=" + java.net.URLEncoder.encode(keyword, java.nio.charset.StandardCharsets.UTF_8);
    }

    private static JsonNode findById(JsonNode array, Integer id) {
        if (array == null || !array.isArray()) return null;
        for (JsonNode n : array) {
            if (n.get("iIncrement").asInt() == id) return n;
        }
        return null;
    }

    private Integer insertOriginal(String procedureName, String nameZh, String createdBy) {
        jdbcTemplate.update(
                "INSERT INTO tModule (sBrandsId, sSubsidiaryId, tCreateDate, sDisplayType, sProcedureName, "
                        + "sModuleType, sManageDeptEn, bShowPermission, sModuleNameZh, iSortOrder, sCreatedBy, bDeleted) "
                        + "VALUES ('XLY','XLY', NOW(), '手机端', ?, '业务模块', 'IT', 0, ?, 0, ?, 0)",
                procedureName, nameZh, createdBy);
        return jdbcTemplate.queryForObject(
                "SELECT iIncrement FROM tModule WHERE sProcedureName = ?", Integer.class, procedureName);
    }

    private static Map<String, Object> updateBody() {
        Map<String, Object> m = new HashMap<>();
        m.put("sDisplayType", "手机端");
        m.put("sModuleType", "业务模块");
        m.put("sManageDeptEn", "IT");
        m.put("bShowPermission", false);
        m.put("sModuleNameZh", "更新后");
        return m;
    }

    private String idUrl(Integer id) {
        return "http://localhost:" + port + "/api/mod/modules/" + id;
    }

    private String url() {
        return "http://localhost:" + port + "/api/mod/modules";
    }

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

    static Map<String, Object> validBody(String procedureName, String nameZh) {
        Map<String, Object> m = new HashMap<>();
        m.put("sDisplayType", "手机端");
        m.put("sProcedureName", procedureName);
        m.put("sModuleType", "业务模块");
        m.put("sManageDeptEn", "IT");
        m.put("bShowPermission", false);
        m.put("sModuleNameZh", nameZh);
        return m;
    }
}
