package com.yimeng.okr.integration;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yimeng.okr.entity.AssessmentPeriod;
import com.yimeng.okr.entity.KpiConfig;
import com.yimeng.okr.entity.PerformanceFeedback;
import com.yimeng.okr.entity.PerformanceScore;
import com.yimeng.okr.entity.ScoreAppeal;
import com.yimeng.okr.entity.SysDepartment;
import com.yimeng.okr.entity.SysUser;
import com.yimeng.okr.enums.PeriodStatus;
import com.yimeng.okr.enums.PeriodType;
import com.yimeng.okr.enums.ScoreType;
import com.yimeng.okr.enums.UserRole;
import com.yimeng.okr.mapper.KpiConfigMapper;
import com.yimeng.okr.mapper.OkrObjectiveMapper;
import com.yimeng.okr.mapper.PerformanceFeedbackMapper;
import com.yimeng.okr.mapper.PerformanceScoreMapper;
import com.yimeng.okr.mapper.ScoreAppealMapper;
import com.yimeng.okr.mapper.SysDepartmentMapper;
import com.yimeng.okr.mapper.SysUserMapper;
import com.yimeng.okr.security.JwtTokenProvider;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.servlet.MockMvc;

import java.time.LocalDate;
import java.util.List;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
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.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@SpringBootTest
@AutoConfigureMockMvc
@TestPropertySource(properties = {
        "spring.datasource.url=jdbc:sqlite:file:okr_api_lifecycle?mode=memory&cache=shared",
        "app.seed.demo-data-enabled=false"
})
class PerformanceLifecycleApiIntegrationTest {

    @Autowired MockMvc mockMvc;
    @Autowired ObjectMapper objectMapper;
    @Autowired JwtTokenProvider tokenProvider;
    @Autowired PasswordEncoder passwordEncoder;
    @Autowired SysUserMapper userMapper;
    @Autowired SysDepartmentMapper departmentMapper;
    @Autowired OkrObjectiveMapper objectiveMapper;
    @Autowired KpiConfigMapper kpiConfigMapper;
    @Autowired PerformanceScoreMapper scoreMapper;
    @Autowired PerformanceFeedbackMapper feedbackMapper;
    @Autowired ScoreAppealMapper appealMapper;

    @Test
    void httpApiSupportsFullPerformanceLifecycle() throws Exception {
        SysUser hr = insertUser("api_hr", "HR", UserRole.HR_ADMIN, null, null);
        SysDepartment dept = insertDepartment();
        SysUser manager = insertUser("api_manager", "主管", UserRole.DEPT_LEADER, dept.getId(), null);
        dept.setLeaderId(manager.getId());
        departmentMapper.updateById(dept);
        SysUser employee = insertUser("api_employee", "员工", UserRole.EMPLOYEE, dept.getId(), manager.getId());

        String hrToken = token(hr);
        String managerToken = token(manager);
        String employeeToken = token(employee);

        Long periodId = postForData(hrToken, "/api/periods", Map.of(
                "name", "API 端到端周期",
                "periodType", PeriodType.MONTHLY.name(),
                "startDate", LocalDate.of(2026, 6, 1).toString()
        )).get("id").asLong();

        putJson(hrToken, "/api/periods/" + periodId + "/status", Map.of("status", PeriodStatus.OKR_ALIGN.name()));

        Long managerOkrId = postForData(managerToken, "/api/okr/submit", okrBody(periodId, "提升团队交付质量", List.of())).get("id").asLong();
        Long employeeOkrId = postForData(employeeToken, "/api/okr/submit",
                okrBody(periodId, "完成核心功能交付", List.of(managerOkrId))).get("id").asLong();
        putJson(managerToken, "/api/okr/" + employeeOkrId + "/review", Map.of("approved", true, "remark", "通过"));

        Long managerKpiId = postForData(hrToken, "/api/kpi/configs", kpiBody(periodId, manager.getId())).get("id").asLong();
        putJson(hrToken, "/api/kpi/configs/" + managerKpiId + "/lock", Map.of());
        Long employeeKpiId = postForData(managerToken, "/api/kpi/configs", kpiBody(periodId, employee.getId())).get("id").asLong();
        putJson(managerToken, "/api/kpi/configs/" + employeeKpiId + "/lock", Map.of());

        putJson(hrToken, "/api/periods/" + periodId + "/status", Map.of("status", PeriodStatus.EXECUTING.name()));
        putJson(hrToken, "/api/periods/" + periodId + "/status", Map.of("status", PeriodStatus.ASSESSING.name()));

        postForData(employeeToken, "/api/scores/self", scoreBody(periodId, null, 54, 32));
        postForData(managerToken, "/api/scores/superior", scoreBody(periodId, employee.getId(), 55, 33));
        postForData(hrToken, "/api/scores/publish", Map.of("periodId", periodId, "userId", employee.getId()));

        Long appealId = postForData(employeeToken, "/api/score-appeals",
                Map.of("periodId", periodId, "reason", "希望复核评分依据")).get("id").asLong();
        putJson(hrToken, "/api/score-appeals/" + appealId + "/resolve",
                Map.of("status", "REJECTED", "reply", "评分依据充分"));
        postForData(employeeToken, "/api/scores/confirm", Map.of("periodId", periodId));

        postForData(managerToken, "/api/feedback/interview", Map.of(
                "periodId", periodId,
                "userId", employee.getId(),
                "interviewNotes", "完成面谈",
                "highlights", "交付稳定",
                "issues", "文档不足",
                "improvements", "补齐设计文档"
        ));
        postForData(employeeToken, "/api/feedback/reply", Map.of(
                "periodId", periodId,
                "employeeFeedback", "已确认"
        ));
        postForData(managerToken, "/api/feedback/close", Map.of("periodId", periodId, "userId", employee.getId()));
        postForData(hrToken, "/api/periods/" + periodId + "/participants/" + manager.getId() + "/exempt",
                Map.of("reason", "管理岗不参与个人评分"));

        putJson(hrToken, "/api/periods/" + periodId + "/status", Map.of("status", PeriodStatus.ARCHIVED.name()));

        AssessmentPeriod archived = objectMapper.convertValue(
                getForData(hrToken, "/api/periods/" + periodId), AssessmentPeriod.class);
        assertEquals(PeriodStatus.ARCHIVED, archived.getStatus());
        assertEquals(1, scoreMapper.selectCount(new LambdaQueryWrapper<PerformanceScore>()
                .eq(PerformanceScore::getPeriodId, periodId)
                .eq(PerformanceScore::getUserId, employee.getId())
                .eq(PerformanceScore::getType, ScoreType.FINAL)
                .isNotNull(PerformanceScore::getConfirmedAt)));
        assertEquals("CLOSED", feedbackMapper.selectOne(new LambdaQueryWrapper<PerformanceFeedback>()
                .eq(PerformanceFeedback::getPeriodId, periodId)
                .eq(PerformanceFeedback::getUserId, employee.getId())).getStatus());
        assertEquals("REJECTED", appealMapper.selectById(appealId).getStatus());
        assertNotNull(objectiveMapper.selectById(employeeOkrId).getSnapshotJson());
        assertNotNull(kpiConfigMapper.selectById(employeeKpiId).getSnapshotJson());
    }

    private JsonNode postForData(String token, String path, Object body) throws Exception {
        String response = mockMvc.perform(post(path)
                        .header("Authorization", "Bearer " + token)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(body)))
                .andExpect(status().isOk())
                .andReturn().getResponse().getContentAsString();
        return objectMapper.readTree(response).get("data");
    }

    private void putJson(String token, String path, Object body) throws Exception {
        mockMvc.perform(put(path)
                        .header("Authorization", "Bearer " + token)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(body)))
                .andExpect(status().isOk());
    }

    private JsonNode getForData(String token, String path) throws Exception {
        String response = mockMvc.perform(get(path)
                        .header("Authorization", "Bearer " + token))
                .andExpect(status().isOk())
                .andReturn().getResponse().getContentAsString();
        return objectMapper.readTree(response).get("data");
    }

    private Map<String, Object> okrBody(Long periodId, String title, List<Long> alignedObjectiveIds) {
        return Map.of(
                "periodId", periodId,
                "title", title,
                "alignedObjectiveIds", alignedObjectiveIds,
                "keyResults", List.of(
                        Map.of("title", "关键结果一", "targetValue", 100, "weight", 50),
                        Map.of("title", "关键结果二", "targetValue", 100, "weight", 50)
                )
        );
    }

    private Map<String, Object> kpiBody(Long periodId, Long userId) {
        return Map.of(
                "periodId", periodId,
                "userId", userId,
                "itemsJson", "[{\"name\":\"交付质量\",\"weight\":100}]"
        );
    }

    private Map<String, Object> scoreBody(Long periodId, Long userId, int okrScore, int kpiScore) {
        if (userId == null) {
            return Map.of("periodId", periodId, "okrScore", okrScore, "kpiScore", kpiScore, "bonus", 0, "penalty", 0, "commentsJson", "{}");
        }
        return Map.of("periodId", periodId, "userId", userId, "okrScore", okrScore, "kpiScore", kpiScore, "bonus", 0, "penalty", 0, "commentsJson", "{}");
    }

    private SysDepartment insertDepartment() {
        SysDepartment department = new SysDepartment();
        department.setName("API研发部" + System.nanoTime());
        department.setParentId(0L);
        departmentMapper.insert(department);
        return department;
    }

    private SysUser insertUser(String username, String realName, UserRole role, Long departmentId, Long superiorId) {
        SysUser user = new SysUser();
        user.setUsername(username + "_" + System.nanoTime());
        user.setPasswordHash(passwordEncoder.encode("Passw0rd!"));
        user.setRealName(realName);
        user.setRole(role);
        user.setDepartmentId(departmentId);
        user.setSuperiorId(superiorId);
        user.setStatus("ACTIVE");
        userMapper.insert(user);
        return user;
    }

    private String token(SysUser user) {
        return tokenProvider.generateAccessToken(user.getId(), user.getUsername(), user.getRole().name());
    }
}
