package com.ruoyi.ccdi.project.mapper;

import org.junit.jupiter.api.Test;

import java.nio.file.Files;
import java.nio.file.Path;

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

class CcdiProjectOverviewMapperSqlTest {

    @Test
    void shouldReadOverviewQueriesFromEmployeeResultTable() throws Exception {
        String xml = Files.readString(Path.of("src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml"));
        String riskPeopleSql = extractSelect(xml, "selectRiskPeopleOverviewPage");
        String riskPeopleExportSql = extractSelect(xml, "selectRiskPeopleOverviewList");
        String topRiskPeopleSql = extractSelect(xml, "selectTopRiskPeopleByProjectId");
        String riskModelCardsSql = extractSelect(xml, "selectRiskModelCardsByProjectId");
        String riskModelPeopleSql = extractSelect(xml, "selectRiskModelPeoplePage");

        assertTrue(xml.contains("<sql id=\"riskPeopleOverviewSelectColumns\">"), xml);
        assertTrue(xml.contains("<sql id=\"riskPeopleOverviewOrderBy\">"), xml);
        assertTrue(riskPeopleSql.contains("from ccdi_project_overview_employee_result"));
        assertTrue(riskPeopleSql.contains("result.project_id = #{query.projectId}"), riskPeopleSql);
        assertTrue(riskPeopleSql.contains("<include refid=\"riskPeopleOverviewSelectColumns\"/>"), riskPeopleSql);
        assertTrue(riskPeopleSql.contains("<include refid=\"riskPeopleOverviewOrderBy\"/>"), riskPeopleSql);
        assertTrue(
            xml.contains(
                "order by risk_level_sort asc, result.model_count desc, result.rule_count desc, result.staff_id_card asc"
            ),
            xml
        );
        assertFalse(riskPeopleSql.contains("limit 10"), riskPeopleSql);
        assertFalse(riskPeopleSql.contains("resolvedEmployeeRiskBaseSql"));

        assertTrue(riskPeopleExportSql.contains("from ccdi_project_overview_employee_result"), riskPeopleExportSql);
        assertTrue(riskPeopleExportSql.contains("result.project_id = #{projectId}"), riskPeopleExportSql);
        assertTrue(riskPeopleExportSql.contains("<include refid=\"riskPeopleOverviewSelectColumns\"/>"), riskPeopleExportSql);
        assertTrue(riskPeopleExportSql.contains("<include refid=\"riskPeopleOverviewOrderBy\"/>"), riskPeopleExportSql);

        assertTrue(topRiskPeopleSql.contains("from ccdi_project_overview_employee_result"));
        assertTrue(topRiskPeopleSql.contains("risk_level_code in ('HIGH', 'MEDIUM')"));
        assertFalse(topRiskPeopleSql.contains("resolvedEmployeeRiskBaseSql"));

        assertTrue(riskModelCardsSql.contains("from ccdi_project_overview_employee_result"));
        assertTrue(riskModelCardsSql.contains("model_hit_summary_json"));
        assertFalse(riskModelCardsSql.contains("resolvedEmployeeRiskBaseSql"));

        assertTrue(riskModelPeopleSql.contains("from ccdi_project_overview_employee_result"));
        assertTrue(riskModelPeopleSql.contains("model_codes_csv"));
        assertFalse(riskModelPeopleSql.contains("resolvedEmployeeRiskBaseSql"));
    }

    @Test
    void shouldAvoidWindowFunctionsForMysql57Compatibility() throws Exception {
        String xml = Files.readString(Path.of("src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml"));

        assertFalse(xml.contains("row_number() over"), xml);
        assertFalse(xml.contains("json_table("), xml);
    }

    @Test
    void shouldExposePersonAnalysisDetailQueries() throws Exception {
        String xml = Files.readString(Path.of("src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml"));
        String basicInfoSql = extractSelect(xml, "selectPersonAnalysisBasicInfo");
        String statementRowsSql = extractSelect(xml, "selectPersonAnalysisStatementRows");
        String objectRowsSql = extractSelect(xml, "selectPersonAnalysisObjectRows");

        assertTrue(basicInfoSql.contains("ccdi_base_staff"), basicInfoSql);
        assertTrue(basicInfoSql.contains("left join sys_dept"), basicInfoSql);
        assertTrue(basicInfoSql.contains("ccdi_project_overview_employee_result"), basicInfoSql);

        assertTrue(statementRowsSql.contains("from ccdi_bank_statement"), statementRowsSql);
        assertTrue(statementRowsSql.contains("ccdi_bank_statement_tag_result"), statementRowsSql);
        assertTrue(statementRowsSql.contains("bs.project_id = #{projectId}"), statementRowsSql);

        assertTrue(objectRowsSql.contains("from ccdi_bank_statement_tag_result"), objectRowsSql);
        assertTrue(objectRowsSql.contains("tr.object_type"), objectRowsSql);
        assertTrue(objectRowsSql.contains("tr.reason_detail"), objectRowsSql);
        assertTrue(objectRowsSql.contains("as reasonDetail"), objectRowsSql);
        assertTrue(objectRowsSql.contains("tr.rule_code"), objectRowsSql);
        assertTrue(objectRowsSql.contains("group by coalesce(tr.object_key, tr.object_type), tr.rule_code"), objectRowsSql);
        assertFalse(objectRowsSql.contains("group_concat(distinct tr.reason_detail"), objectRowsSql);
        assertFalse(objectRowsSql.contains("group_concat(distinct tr.rule_name"), objectRowsSql);
        assertTrue(objectRowsSql.contains("tr.staff_id_card = #{staffIdCard}") || objectRowsSql.contains("#{staffIdCard}"), objectRowsSql);
    }

    @Test
    void shouldExposeSuspiciousTransactionAggregationQuery() throws Exception {
        String xml = Files.readString(Path.of("src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml"));
        String suspiciousSql = extractSelect(xml, "selectSuspiciousTransactionPage");
        String modelHitSql = extractSqlFragment(xml, "suspiciousTransactionModelHitSql");
        String modelScopeSql = extractSqlFragment(xml, "suspiciousTransactionModelScopeSql");
        String mergedSql = extractSqlFragment(xml, "suspiciousTransactionMergedSql");
        String aggregatedSql = extractSqlFragment(xml, "suspiciousTransactionAggregatedSql");
        String externalSubjectExistsSql = extractSelect(xml, "selectExternalPersonSubjectExistsByProjectId");

        assertTrue(modelHitSql.contains("from ccdi_bank_statement_tag_result tr"), modelHitSql);
        assertTrue(modelHitSql.contains("tr.bank_statement_id is not null"), modelHitSql);
        assertFalse(modelHitSql.contains("rule_name like '%可疑%'"), modelHitSql);
        assertFalse(modelHitSql.contains("ABNORMAL_CUSTOMER_TRANSACTION"), modelHitSql);
        assertTrue(modelHitSql.contains("suspiciousTransactionModelScopeSql"), modelHitSql);
        assertTrue(modelScopeSql.contains("tr.model_code = #{query.modelCode}"), modelScopeSql);
        assertTrue(modelScopeSql.contains("left(tr.model_code, 9) != 'EXTERNAL_'"), modelScopeSql);
        assertTrue(modelScopeSql.contains("left(tr.model_code, 9) = 'EXTERNAL_'"), modelScopeSql);
        assertTrue(suspiciousSql.contains("ccdi_biz_intermediary"), suspiciousSql);
        assertTrue(suspiciousSql.contains("ccdi_enterprise_base_info"), suspiciousSql);
        assertTrue(suspiciousSql.contains("group by merged.bankStatementId"), suspiciousSql);
        assertTrue(aggregatedSql.contains("lpad(merged.matchPriority, 2, '0')"), aggregatedSql);
        assertTrue(suspiciousSql.contains("hasModelRuleHit"), suspiciousSql);
        assertTrue(suspiciousSql.contains("hasNameListHit"), suspiciousSql);
        assertTrue(suspiciousSql.contains("final_result.nameListHitType"), suspiciousSql);
        assertTrue(suspiciousSql.contains("bs.LE_ACCOUNT_NO as leAccountNo"), suspiciousSql);
        assertTrue(suspiciousSql.contains("bs.CUSTOMER_ACCOUNT_NO as customerAccountNo"), suspiciousSql);
        assertTrue(suspiciousSql.contains("bs.bank_statement_id = final_result.bankStatementId"), suspiciousSql);
        assertTrue(mergedSql.contains("<if test=\"query.includeExternalPerson == true\">"), mergedSql);
        assertTrue(mergedSql.contains("<include refid=\"externalSuspiciousTransactionSql\"/>"), mergedSql);
        assertTrue(externalSubjectExistsSql.contains("limit 1"), externalSubjectExistsSql);
        assertTrue(externalSubjectExistsSql.contains("staff.id_card is null"), externalSubjectExistsSql);
        assertTrue(externalSubjectExistsSql.contains("relation.relation_cert_no is null"), externalSubjectExistsSql);

        String reportSuspiciousSql = extractSelect(xml, "selectReportSuspiciousTransactionList");
        assertTrue(reportSuspiciousSql.contains("final_result.nameListHitType = '中介'"), reportSuspiciousSql);
        assertTrue(reportSuspiciousSql.contains("疑似与中介往来"), reportSuspiciousSql);
        assertTrue(reportSuspiciousSql.contains("final_result.nameListHitType = '信贷客户'"), reportSuspiciousSql);
        assertTrue(reportSuspiciousSql.contains("与信贷客户之间非正常资金往来"), reportSuspiciousSql);
        assertTrue(reportSuspiciousSql.contains("suspiciousTransactionModelScopeSql"), reportSuspiciousSql);
    }

    @Test
    void externalPersonSourceSql_shouldIncludeStatementAndObjectHits() throws Exception {
        String xml = Files.readString(Path.of("src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml"));
        String externalSourceSql = extractSqlFragment(xml, "externalPersonSourceSql");
        String normalizedExternalSourceSql = externalSourceSql.replace("\r\n", "\n");

        assertTrue(externalSourceSql.contains("tr.bank_statement_id = bs.bank_statement_id"), externalSourceSql);
        assertTrue(externalSourceSql.contains("union all"), externalSourceSql);
        assertTrue(externalSourceSql.contains("tr.object_type = 'EXTERNAL_CERT_NO'"), externalSourceSql);
        assertTrue(externalSourceSql.contains("tr.object_key = subject.cert_no"), externalSourceSql);
        assertTrue(externalSourceSql.contains("'资金' as related_object"), externalSourceSql);
        assertFalse(externalSourceSql.contains("counter_staff.id_card = bs.customer_cert_no"), externalSourceSql);
        assertFalse(externalSourceSql.contains("counter_relation.relation_cert_no = bs.customer_cert_no"), externalSourceSql);
        assertFalse(externalSourceSql.contains("counter_intermediary.person_id = bs.customer_cert_no"), externalSourceSql);
        assertInOrder(
            externalSourceSql,
            "when counter_account.owner_type = 'EMPLOYEE' then '员工'",
            "when counter_account.owner_type = 'RELATION' then '员工亲属'",
            "when counter_account.owner_type = 'CREDIT_CUSTOMER' then '信贷客户'",
            "when counter_account.owner_type = 'INTERMEDIARY' then '中介库人员'",
            "when counter_staff.id_card is not null then '员工'",
            "when counter_relation.relation_cert_no is not null then '员工亲属'"
        );
        assertTrue(normalizedExternalSourceSql.contains("left join ccdi_base_staff counter_staff\n            on counter_account.account_no is null"), externalSourceSql);
        assertTrue(normalizedExternalSourceSql.contains("left join ccdi_staff_fmy_relation counter_relation\n            on counter_account.account_no is null"), externalSourceSql);
    }

    @Test
    void suspiciousTransactionNameListSql_shouldKeepCreditCustomerAndIntermediaryRulesScopedByAmount() throws Exception {
        String xml = Files.readString(Path.of("src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml"));
        String nameHitSql = extractSqlFragment(xml, "suspiciousTransactionNameHitSql");
        String aggregatedSql = extractSqlFragment(xml, "suspiciousTransactionAggregatedSql");

        assertTrue(nameHitSql.contains("account.owner_type = 'CREDIT_CUSTOMER'"), nameHitSql);
        assertTrue(nameHitSql.contains("account.owner_type = 'INTERMEDIARY'"), nameHitSql);
        assertTrue(nameHitSql.contains("account.account_no = bs.customer_account_no"), nameHitSql);
        assertTrue(nameHitSql.contains("'信贷客户' as nameListHitType"), nameHitSql);
        assertTrue(nameHitSql.contains("'中介' as nameListHitType"), nameHitSql);
        assertTrue(nameHitSql.contains("intermediary.name = bs.CUSTOMER_ACCOUNT_NAME"), nameHitSql);
        assertTrue(nameHitSql.contains("enterprise.ent_source = 'INTERMEDIARY'"), nameHitSql);
        assertTrue(
            nameHitSql.contains("GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) > 1000"),
            nameHitSql
        );
        assertFalse(nameHitSql.contains("customer_cert_no"), nameHitSql);
        assertFalse(nameHitSql.contains("social_credit_code = bs"), nameHitSql);
        assertTrue(aggregatedSql.contains("group by merged.bankStatementId"), aggregatedSql);
        assertTrue(aggregatedSql.contains("max(merged.hasModelRuleHit) as hasModelRuleHit"), aggregatedSql);
        assertTrue(aggregatedSql.contains("max(merged.hasNameListHit) as hasNameListHit"), aggregatedSql);
    }

    @Test
    void shouldExposeEmployeeCreditNegativeQuery() throws Exception {
        String xml = Files.readString(Path.of("src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml"));
        String employeeCreditSql = extractSelect(xml, "selectEmployeeCreditNegativePage");
        String employeeCreditExportSql = extractSelect(xml, "selectEmployeeCreditNegativeList");

        assertTrue(employeeCreditSql.contains("from ccdi_project_overview_employee_result"), employeeCreditSql);
        assertTrue(employeeCreditSql.contains("inner join ccdi_credit_negative_info"), employeeCreditSql);
        assertTrue(employeeCreditSql.contains("result.project_id = #{query.projectId}"), employeeCreditSql);
        assertTrue(employeeCreditSql.contains("order by neg.query_date desc, neg.person_id asc"), employeeCreditSql);
        assertFalse(employeeCreditSql.contains("ccdi_debts_info"), employeeCreditSql);

        assertTrue(employeeCreditExportSql.contains("from ccdi_project_overview_employee_result"), employeeCreditExportSql);
        assertTrue(employeeCreditExportSql.contains("inner join ccdi_credit_negative_info"), employeeCreditExportSql);
        assertTrue(employeeCreditExportSql.contains("result.project_id = #{projectId}"), employeeCreditExportSql);
        assertTrue(
            employeeCreditExportSql.contains("order by neg.query_date desc, neg.person_id asc"),
            employeeCreditExportSql
        );
        assertFalse(employeeCreditExportSql.contains("ccdi_debts_info"), employeeCreditExportSql);
    }

    @Test
    void shouldExposeAbnormalAccountQueries() throws Exception {
        String xml = Files.readString(Path.of("src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml"));
        String abnormalPageSql = extractSelect(xml, "selectAbnormalAccountPage");
        String abnormalExportSql = extractSelect(xml, "selectAbnormalAccountList");

        assertTrue(abnormalPageSql.contains("tr.model_code = 'ABNORMAL_ACCOUNT'"), abnormalPageSql);
        assertTrue(abnormalPageSql.contains("tr.bank_statement_id is null"), abnormalPageSql);
        assertTrue(abnormalPageSql.contains("account.owner_type = 'EMPLOYEE'"), abnormalPageSql);
        assertTrue(abnormalPageSql.contains("tr.reason_detail"), abnormalPageSql);
        assertTrue(abnormalPageSql.contains("abnormal.reasonDetail"), abnormalPageSql);
        assertTrue(abnormalPageSql.contains("abnormal.involved_amount"), abnormalPageSql);
        assertTrue(abnormalPageSql.contains("instr(tr.reason_detail, account.account_no) > 0"), abnormalPageSql);
        assertTrue(abnormalPageSql.contains("when account.status = 1 then '正常'"), abnormalPageSql);
        assertTrue(abnormalPageSql.contains("when account.status = 2 then '已销户'"), abnormalPageSql);
        assertTrue(abnormalPageSql.contains("when tr.rule_code = 'SUDDEN_ACCOUNT_CLOSURE'"), abnormalPageSql);
        assertTrue(abnormalPageSql.contains("when tr.rule_code = 'DORMANT_ACCOUNT_LARGE_ACTIVATION'"), abnormalPageSql);
        assertTrue(
            abnormalPageSql.contains("order by abnormal_time desc, account.account_no asc, tr.rule_code asc"),
            abnormalPageSql
        );

        assertTrue(abnormalExportSql.contains("tr.model_code = 'ABNORMAL_ACCOUNT'"), abnormalExportSql);
        assertTrue(abnormalExportSql.contains("tr.bank_statement_id is null"), abnormalExportSql);
        assertTrue(abnormalExportSql.contains("account.owner_type = 'EMPLOYEE'"), abnormalExportSql);
        assertTrue(abnormalExportSql.contains("tr.reason_detail"), abnormalExportSql);
        assertTrue(abnormalExportSql.contains("abnormal.reasonDetail"), abnormalExportSql);
        assertTrue(abnormalExportSql.contains("abnormal.involved_amount"), abnormalExportSql);
        assertTrue(
            abnormalExportSql.contains("order by abnormal_time desc, account.account_no asc, tr.rule_code asc"),
            abnormalExportSql
        );

        assertTrue(xml.contains("tr.reason_detail as reasonDetail"), xml);
        assertTrue(xml.contains("as involved_amount"), xml);
    }

    @Test
    void shouldExposeUnsubmittedAccountQueriesWithLoanExclusion() throws Exception {
        String xml = Files.readString(Path.of("src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml"));
        String accountSql = extractSelect(xml, "selectUnsubmittedAccountList");
        String flowSql = extractSelect(xml, "selectUnsubmittedAccountFlowList");
        String candidateSql = extractSqlFragment(xml, "unsubmittedAccountCandidateRowSql");
        String loanSql = extractSqlFragment(xml, "unsubmittedAccountLoanConditionSql");
        String aggregateSql = extractSqlFragment(xml, "unsubmittedAccountAggregateSql");

        assertTrue(accountSql.contains("unsubmittedAccountAggregateSql"), accountSql);
        assertTrue(accountSql.contains("unsubmittedAccountSubjectTotalSql"), accountSql);
        assertTrue(flowSql.contains("unsubmittedAccountCandidateRowSql"), flowSql);
        assertTrue(flowSql.contains("unsubmittedAccountAggregateSql"), flowSql);
        assertTrue(flowSql.contains("query.projectId"), flowSql);
        assertTrue(flowSql.contains("query.subjectName"), flowSql);
        assertTrue(flowSql.contains("query.accountNo"), flowSql);
        assertTrue(flowSql.contains("candidate.subjectName"), flowSql);
        assertTrue(flowSql.contains("candidate.accountNo"), flowSql);
        assertTrue(candidateSql.contains("bs.project_id = #{unsubmittedProjectId}"), candidateSql);
        assertTrue(candidateSql.contains("bs.CUSTOMER_ACCOUNT_NAME"), candidateSql);
        assertTrue(candidateSql.contains("bs.LE_ACCOUNT_NAME"), candidateSql);
        assertTrue(candidateSql.contains("not exists"), candidateSql);
        assertTrue(candidateSql.contains("submitted.LE_ACCOUNT_NO"), candidateSql);
        assertFalse(candidateSql.contains("TRX_TYPE"), candidateSql);

        assertTrue(loanSql.contains("还贷"), loanSql);
        assertTrue(loanSql.contains("贷款还款"), loanSql);
        assertTrue(loanSql.contains("归还贷款"), loanSql);
        assertTrue(loanSql.contains("贷款发放"), loanSql);
        assertTrue(loanSql.contains("普贷放款"), loanSql);
        assertTrue(loanSql.contains("信贷系统业务对公资产"), loanSql);
        assertTrue(loanSql.contains("个贷系统平账专户"), loanSql);
        assertTrue(loanSql.contains("企业贷"), loanSql);
        assertTrue(loanSql.contains("消费金融"), loanSql);
        assertFalse(loanSql.contains("民间借贷"), loanSql);
        assertFalse(loanSql.contains("资金拆借"), loanSql);
        assertFalse(loanSql.contains("调拨"), loanSql);
        assertFalse(loanSql.contains("划拨"), loanSql);
        assertFalse(loanSql.contains("TRX_TYPE"), loanSql);

        assertTrue(aggregateSql.contains("having sum(candidate.loanFlag) &lt; count(*)"), aggregateSql);
    }

    private String extractSelect(String xml, String selectId) {
        String start = "<select id=\"" + selectId + "\"";
        int startIndex = xml.indexOf(start);
        assertTrue(startIndex >= 0, "missing select: " + selectId);
        int endIndex = xml.indexOf("</select>", startIndex);
        assertTrue(endIndex >= 0, "missing closing select tag: " + selectId);
        return xml.substring(startIndex, endIndex);
    }

    private String extractSqlFragment(String xml, String sqlId) {
        String start = "<sql id=\"" + sqlId + "\"";
        int startIndex = xml.indexOf(start);
        assertTrue(startIndex >= 0, "missing sql fragment: " + sqlId);
        int endIndex = xml.indexOf("</sql>", startIndex);
        assertTrue(endIndex >= 0, "missing closing sql tag: " + sqlId);
        return xml.substring(startIndex, endIndex);
    }

    private void assertInOrder(String sql, String... fragments) {
        int previousIndex = -1;
        for (String fragment : fragments) {
            int currentIndex = sql.indexOf(fragment);
            assertTrue(currentIndex > previousIndex, () -> "fragment order mismatch: " + fragment + "\n" + sql);
            previousIndex = currentIndex;
        }
    }
}
