package com.ruoyi.ccdi.project.service.impl;

import com.ruoyi.ccdi.project.domain.CcdiProject;
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectAbnormalAccountExcel;
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectEmployeeCreditNegativeExcel;
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectExternalPersonWarningExcel;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportModelSummaryVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportParamVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportSuspiciousTransactionVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportUploadSubjectVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewDashboardVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewStatVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExternalRiskSummaryVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskHitTagVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelPeopleItemVO;
import com.ruoyi.common.exception.ServiceException;
import java.io.File;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockHttpServletResponse;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

class CcdiProjectOverviewReportPdfExporterTest {

    @Test
    void shouldExportOverviewReportPdf() throws Exception {
        CcdiProjectOverviewReportPdfExporter exporter = new CcdiProjectOverviewReportPdfExporter(resolveTestFontPath());
        MockHttpServletResponse response = new MockHttpServletResponse();

        exporter.export(response, buildReport());

        assertEquals("application/pdf", response.getContentType());
        String header = new String(response.getContentAsByteArray(), 0, 4, StandardCharsets.ISO_8859_1);
        assertEquals("%PDF", header);
        assertTrue(response.getContentAsByteArray().length > 1000);
    }

    @Test
    void shouldUseOverallRiskSummaryInReportMetrics() throws Exception {
        CcdiProjectOverviewReportPdfExporter exporter = new CcdiProjectOverviewReportPdfExporter(resolveTestFontPath());
        CcdiProjectOverviewReportVO report = buildReport();
        report.setExternalRiskSummary(buildExternalSummary());

        List<CcdiProjectOverviewStatVO> stats = invokeOverallRiskMetrics(exporter, report);

        assertEquals(12, stats.get(0).getValue());
        assertEquals(3, stats.get(1).getValue());
        assertEquals(4, stats.get(2).getValue());
        assertEquals(1, stats.get(3).getValue());
        assertEquals("无风险", stats.get(4).getLabel());
        assertEquals(4, stats.get(4).getValue());
    }

    @Test
    void shouldHideExternalSectionWhenReportHasOnlyEmployees() throws Exception {
        CcdiProjectOverviewReportPdfExporter exporter = new CcdiProjectOverviewReportPdfExporter(resolveTestFontPath());
        MockHttpServletResponse response = new MockHttpServletResponse();
        CcdiProjectOverviewReportVO report = buildReport();

        exporter.export(response, report);

        assertEquals("application/pdf", response.getContentType());
        assertFalse(invokeHasExternalRisk(exporter, report));
    }

    @Test
    void shouldShowExternalSectionWhenExternalRiskExists() throws Exception {
        CcdiProjectOverviewReportPdfExporter exporter = new CcdiProjectOverviewReportPdfExporter(resolveTestFontPath());
        MockHttpServletResponse response = new MockHttpServletResponse();
        CcdiProjectOverviewReportVO report = buildReport();
        report.setExternalRiskSummary(buildExternalSummary());
        report.setExternalModelSummaries(List.of(buildExternalModelSummary()));
        report.setExternalPersonWarnings(List.of(buildExternalPersonWarning()));

        exporter.export(response, report);
        List<CcdiProjectOverviewStatVO> externalMetrics = invokeExternalMetrics(exporter, report);

        assertEquals("application/pdf", response.getContentType());
        assertTrue(invokeHasExternalRisk(exporter, report));
        assertEquals("外部人员", externalMetrics.get(0).getLabel());
        assertEquals(2, externalMetrics.get(0).getValue());
        assertEquals("高风险", externalMetrics.get(1).getLabel());
        assertEquals(1, externalMetrics.get(1).getValue());
    }

    @Test
    void tableGap_shouldLeaveEnoughSpaceForNextSectionTitle() throws Exception {
        Class<?> writerClass = Class.forName(
            "com.ruoyi.ccdi.project.service.impl.CcdiProjectOverviewReportPdfExporter$PdfPageWriter"
        );
        float tableAfterGap = readPrivateFloat(writerClass, "TABLE_AFTER_GAP");
        float sectionFontSize = readPrivateFloat(writerClass, "SECTION_FONT_SIZE");

        assertTrue(tableAfterGap > sectionFontSize);
    }

    @SuppressWarnings("unchecked")
    private List<CcdiProjectOverviewStatVO> invokeOverallRiskMetrics(
        CcdiProjectOverviewReportPdfExporter exporter,
        CcdiProjectOverviewReportVO report
    ) throws Exception {
        Method method = CcdiProjectOverviewReportPdfExporter.class.getDeclaredMethod(
            "buildOverallRiskMetrics",
            CcdiProjectOverviewReportVO.class
        );
        method.setAccessible(true);
        return (List<CcdiProjectOverviewStatVO>) method.invoke(exporter, report);
    }

    @SuppressWarnings("unchecked")
    private List<CcdiProjectOverviewStatVO> invokeExternalMetrics(
        CcdiProjectOverviewReportPdfExporter exporter,
        CcdiProjectOverviewReportVO report
    ) throws Exception {
        Method method = CcdiProjectOverviewReportPdfExporter.class.getDeclaredMethod(
            "buildExternalMetrics",
            CcdiProjectOverviewReportVO.class
        );
        method.setAccessible(true);
        return (List<CcdiProjectOverviewStatVO>) method.invoke(exporter, report);
    }

    private boolean invokeHasExternalRisk(
        CcdiProjectOverviewReportPdfExporter exporter,
        CcdiProjectOverviewReportVO report
    ) throws Exception {
        Method method = CcdiProjectOverviewReportPdfExporter.class.getDeclaredMethod(
            "hasExternalRisk",
            CcdiProjectOverviewReportVO.class
        );
        method.setAccessible(true);
        return (Boolean) method.invoke(exporter, report);
    }

    @Test
    void shouldRejectBlankPdfFontPath() {
        CcdiProjectOverviewReportPdfExporter exporter = new CcdiProjectOverviewReportPdfExporter("");
        MockHttpServletResponse response = new MockHttpServletResponse();

        ServiceException exception = assertThrows(ServiceException.class, () -> exporter.export(response, buildReport()));

        assertEquals("未配置PDF中文字体路径，无法导出PDF报告", exception.getMessage());
    }

    @Test
    void shouldRejectUnavailablePdfFontPath() {
        String missingPath = "/tmp/ccdi-missing-pdf-font-" + System.nanoTime() + ".ttc";
        CcdiProjectOverviewReportPdfExporter exporter = new CcdiProjectOverviewReportPdfExporter(missingPath);
        MockHttpServletResponse response = new MockHttpServletResponse();

        ServiceException exception = assertThrows(ServiceException.class, () -> exporter.export(response, buildReport()));

        assertTrue(exception.getMessage().contains(missingPath));
    }

    private String resolveTestFontPath() {
        List<String> candidates = List.of(
            "C:/Windows/Fonts/msyh.ttc",
            "C:/Windows/Fonts/simhei.ttf",
            "C:/Windows/Fonts/simsun.ttc",
            "/System/Library/Fonts/STHeiti Medium.ttc",
            "/System/Library/Fonts/STHeiti Light.ttc",
            "/System/Library/Fonts/Hiragino Sans GB.ttc",
            "/System/Library/Fonts/Supplemental/Songti.ttc",
            "/usr/share/fonts/truetype/wqy/wqy-microhei.ttc"
        );
        return candidates.stream()
            .filter(path -> new File(path).isFile())
            .findFirst()
            .orElse(candidates.get(0));
    }

    private CcdiProjectOverviewReportVO buildReport() {
        CcdiProjectOverviewReportVO report = new CcdiProjectOverviewReportVO();
        CcdiProject project = new CcdiProject();
        project.setProjectId(40L);
        project.setProjectName("导入测试");
        report.setProject(project);
        report.setDashboard(buildDashboard());
        report.setUploadSubjects(List.of(buildUploadSubject()));
        report.setParams(List.of(buildParam()));
        report.setModelSummaries(List.of(buildModelSummary()));
        report.setRiskPeople(List.of(buildRiskPeople()));
        report.setSuspiciousTransactions(List.of(buildSuspiciousTransaction()));
        report.setIllegalPeople(List.of(buildIllegalPerson()));
        report.setAbnormalAccounts(List.of(buildAbnormalAccount()));
        return report;
    }

    private float readPrivateFloat(Class<?> clazz, String fieldName) throws Exception {
        java.lang.reflect.Field field = clazz.getDeclaredField(fieldName);
        field.setAccessible(true);
        return field.getFloat(null);
    }

    private CcdiProjectOverviewDashboardVO buildDashboard() {
        CcdiProjectOverviewDashboardVO dashboard = new CcdiProjectOverviewDashboardVO();
        dashboard.setStats(List.of(
            buildStat("people", "总人数", 10),
            buildStat("riskPeople", "高风险", 2),
            buildStat("medium", "中风险", 3),
            buildStat("low", "低风险", 1),
            buildStat("count", "无风险人员", 4)
        ));
        return dashboard;
    }

    private CcdiProjectOverviewStatVO buildStat(String key, String label, Integer value) {
        CcdiProjectOverviewStatVO stat = new CcdiProjectOverviewStatVO();
        stat.setKey(key);
        stat.setLabel(label);
        stat.setValue(value);
        return stat;
    }

    private CcdiProjectExternalRiskSummaryVO buildExternalSummary() {
        CcdiProjectExternalRiskSummaryVO summary = new CcdiProjectExternalRiskSummaryVO();
        summary.setTotal(2);
        summary.setHigh(1);
        summary.setMedium(1);
        summary.setLow(0);
        summary.setNoRisk(0);
        return summary;
    }

    private CcdiProjectOverviewReportUploadSubjectVO buildUploadSubject() {
        CcdiProjectOverviewReportUploadSubjectVO row = new CcdiProjectOverviewReportUploadSubjectVO();
        row.setSubjectName("测试主体");
        row.setAccountNos("6222000000000001、6222000000000002");
        row.setDataPeriod("3个月");
        row.setFileCount(2);
        return row;
    }

    private CcdiProjectOverviewReportParamVO buildParam() {
        CcdiProjectOverviewReportParamVO row = new CcdiProjectOverviewReportParamVO();
        row.setModelName("大额交易模型");
        row.setParamName("单笔大额转账金额");
        row.setParamValue("5000");
        row.setParamUnit("元");
        row.setParamDesc("单笔转账金额超过");
        return row;
    }

    private CcdiProjectOverviewReportModelSummaryVO buildModelSummary() {
        CcdiProjectOverviewReportModelSummaryVO row = new CcdiProjectOverviewReportModelSummaryVO();
        row.setModelName("大额交易");
        row.setWarningCount(6);
        row.setPeopleCount(2);
        row.setPeopleNames("张三、李四");
        return row;
    }

    private CcdiProjectOverviewReportModelSummaryVO buildExternalModelSummary() {
        CcdiProjectOverviewReportModelSummaryVO row = new CcdiProjectOverviewReportModelSummaryVO();
        row.setModelName("外部人员异常交易");
        row.setWarningCount(2);
        row.setPeopleCount(2);
        row.setPeopleNames("-");
        return row;
    }

    private CcdiProjectRiskModelPeopleItemVO buildRiskPeople() {
        CcdiProjectRiskModelPeopleItemVO row = new CcdiProjectRiskModelPeopleItemVO();
        row.setStaffName("张三");
        row.setStaffCode("1001");
        row.setIdNo("330000000000000001");
        row.setDepartment("财务部");
        row.setModelNames(List.of("大额交易"));
        CcdiProjectRiskHitTagVO tag = new CcdiProjectRiskHitTagVO();
        tag.setRuleName("大额转账交易");
        row.setHitTagList(List.of(tag));
        return row;
    }

    private CcdiProjectExternalPersonWarningExcel buildExternalPersonWarning() {
        CcdiProjectExternalPersonWarningExcel row = new CcdiProjectExternalPersonWarningExcel();
        row.setName("外部人员甲");
        row.setIdNo("330000000000000003");
        row.setSubjectType("外部人员");
        row.setRiskLevel("高风险");
        row.setModelCount(1);
        row.setRiskPoint("疑似异常往来");
        row.setRelatedObject("张三");
        row.setLatestTradeTime("2026-06-25 10:00:00");
        return row;
    }

    private CcdiProjectOverviewReportSuspiciousTransactionVO buildSuspiciousTransaction() {
        CcdiProjectOverviewReportSuspiciousTransactionVO row = new CcdiProjectOverviewReportSuspiciousTransactionVO();
        row.setTrxDate("2026-03-20 10:00:00");
        row.setLeAccountNo("6222000000000001");
        row.setLeAccountName("测试主体");
        row.setCustomerAccountNo("6222000000000002");
        row.setCustomerAccountName("对方账户");
        row.setRelatedStaffName("张三");
        row.setRelatedStaffCode("1001");
        row.setUserMemo("转账");
        row.setCashType("支出");
        row.setHitTags("大额转账交易");
        row.setDisplayAmount(new BigDecimal("-5000.00"));
        return row;
    }

    private CcdiProjectEmployeeCreditNegativeExcel buildIllegalPerson() {
        CcdiProjectEmployeeCreditNegativeExcel row = new CcdiProjectEmployeeCreditNegativeExcel();
        row.setPersonName("李四");
        row.setPersonId("330000000000000002");
        row.setQueryDate("2026-03-20");
        row.setCivilCnt(1);
        row.setCivilLmt(new BigDecimal("20000.00"));
        return row;
    }

    private CcdiProjectAbnormalAccountExcel buildAbnormalAccount() {
        CcdiProjectAbnormalAccountExcel row = new CcdiProjectAbnormalAccountExcel();
        row.setAccountNo("6222000000000003");
        row.setAccountName("王五");
        row.setBankName("中国银行");
        row.setAbnormalType("突然销户");
        row.setAbnormalTime("2026-03-20");
        row.setStatus("已销户");
        row.setReasonDetail("账户6222000000000003销户前存在异常交易");
        row.setInvolvedAmount(new BigDecimal("120000.00"));
        return row;
    }
}
