from __future__ import annotations

import os
from pathlib import Path

from docx import Document
from docx.shared import Pt

import build_official_v3 as fmt


SOURCE_DIR = Path(
    os.environ.get(
        "CODE_AUDIT_V5_OUTPUT_DIR",
        str(Path.home() / "Desktop/等保整改证明材料-20260916/01-代码审计/对外报送修订版V5"),
    )
)
OUTPUT_DIR = Path(
    os.environ.get(
        "CODE_AUDIT_V6_OUTPUT_DIR",
        str(Path.home() / "Desktop/等保整改证明材料-20260916/01-代码审计/主体补充完善版V6"),
    )
)
REPORT_NAME = "智慧营养健康餐厅管理系统重点代码安全审查报告.docx"
RECORD_NAME = "智慧营养健康餐厅管理系统代码安全审查记录及证明材料.docx"


def find_paragraph(doc: Document, exact_text: str):
    matches = [paragraph for paragraph in doc.paragraphs if paragraph.text == exact_text]
    if len(matches) != 1:
        raise ValueError(f"段落锚点数量不是 1：{exact_text!r}，实际 {len(matches)}")
    return matches[0]


def replace_paragraph(doc: Document, old_text: str, new_text: str) -> None:
    paragraph = find_paragraph(doc, old_text)
    for run in paragraph.runs:
        run.text = ""
    run = paragraph.runs[0] if paragraph.runs else paragraph.add_run()
    run.text = new_text
    fmt.style_run(run, font=fmt.BODY_FONT, size=14)


def insert_body_after(doc: Document, anchor_text: str, new_text: str) -> None:
    anchor = find_paragraph(doc, anchor_text)
    fmt.add_text(doc, new_text, first_line=True, line_pt=24, size=14)
    paragraph = doc.paragraphs[-1]
    anchor._p.addnext(paragraph._p)


def remove_page_break_before(doc: Document, heading_text: str) -> None:
    heading = find_paragraph(doc, heading_text)
    previous = heading._p.getprevious()
    if previous is None or 'w:type="page"' not in previous.xml:
        raise ValueError(f"未找到标题前的分页符：{heading_text!r}")
    previous.getparent().remove(previous)


def compact_paragraph(doc: Document, exact_text: str, *, size: float, line: float, before: float = 0) -> None:
    paragraph = find_paragraph(doc, exact_text)
    paragraph.paragraph_format.line_spacing = Pt(line)
    paragraph.paragraph_format.space_before = Pt(before)
    for run in paragraph.runs:
        fmt.style_run(run, font=fmt.BODY_FONT, size=size)


def compact_last_paragraph(doc: Document, exact_text: str, *, size: float, line: float, before: float = 0) -> None:
    matches = [paragraph for paragraph in doc.paragraphs if paragraph.text == exact_text]
    if not matches:
        raise ValueError(f"未找到段落：{exact_text!r}")
    paragraph = matches[-1]
    paragraph.paragraph_format.line_spacing = Pt(line)
    paragraph.paragraph_format.space_before = Pt(before)
    for run in paragraph.runs:
        fmt.style_run(run, font=fmt.BODY_FONT, size=size)


def compact_table(table, *, size: float, line: float) -> None:
    for row_index, row in enumerate(table.rows):
        for cell in row.cells:
            for paragraph in cell.paragraphs:
                paragraph.paragraph_format.line_spacing = Pt(line)
                for run in paragraph.runs:
                    fmt.style_run(
                        run,
                        font=fmt.HEADING_FONT if row_index == 0 else fmt.BODY_FONT,
                        size=size,
                    )


def find_table(doc: Document, headers: list[str]):
    matches = [
        table
        for table in doc.tables
        if [cell.text for cell in table.rows[0].cells] == headers
    ]
    if len(matches) != 1:
        raise ValueError(f"表格锚点数量不是 1：{headers!r}，实际 {len(matches)}")
    return matches[0]


def set_cell_text(cell, text: str, *, size: float = 10.2) -> None:
    paragraph = cell.paragraphs[0]
    for run in paragraph.runs:
        run.text = ""
    run = paragraph.runs[0] if paragraph.runs else paragraph.add_run()
    run.text = text
    fmt.style_run(run, font=fmt.BODY_FONT, size=size)


def update_row(table, row_label: str, values: list[str], *, size: float = 10.2) -> None:
    matches = [row for row in table.rows[1:] if row.cells[0].text == row_label]
    if len(matches) != 1:
        raise ValueError(f"表格行锚点数量不是 1：{row_label!r}，实际 {len(matches)}")
    if len(values) != len(matches[0].cells):
        raise ValueError(f"表格列数不一致：{row_label!r}")
    for cell, text in zip(matches[0].cells, values):
        set_cell_text(cell, text, size=size)


def revise_report() -> None:
    source = SOURCE_DIR / REPORT_NAME
    target = OUTPUT_DIR / REPORT_NAME
    doc = Document(source)

    old_work = (
        "一是围绕登录入口、会话处理、人员信息访问、日期配餐查询和系统配置加载等重点路径进行代码追踪；"
        "二是检查服务端输入校验、对象权限校验、查询构造、令牌保护和口令存储方式；三是核对历史人员详情接口修复、"
        "定向测试和服务器文件检查记录；四是依据现行安全开发、变更和授权审批制度登记检查结果及后续处置事项。"
    )
    new_work = (
        "本次检查以登录和会话、人员信息接口、日期配餐查询、系统配置加载等重点路径为主线：身份鉴别方面核对登录失败处理、"
        "密码使用控制和二次认证状态管理；对象权限方面检查人员详情、更新、删除及展示接口是否在服务端校验目标人员范围；"
        "输入与查询方面检查请求参数校验和查询条件构造；敏感配置方面检查凭据是否以明文方式进入代码和配置；会话与口令方面"
        "检查令牌保护、账号状态复核、摘要算法和盐值强度。"
    )
    replace_paragraph(doc, old_work, new_work)
    insert_body_after(
        doc,
        new_work,
        "本项工作按照审查对象、检查环节、实际发现和处置进展逐项记录，并核对历史人员详情接口修复、定向测试及服务器文件检查结果。"
        "已提供的安全开发管理规定要求在安全需求、阶段评审、代码检查和上线前测试环节落实控制；变更管理规定要求开展影响分析、测试、"
        "回退和验收；授权和审批管理规定要求相关安全活动及程序变更履行审批。上述内容作为后续整改和复核的管理要求，不替代实际执行记录。",
    )

    issue_table = find_table(doc, ["序号", "检查发现", "当前处置情况"])
    update_row(issue_table, "1", ["1", "二次认证完成状态与服务端当前用户、会话的绑定不足", "待完善服务端认证状态管理并复核"])
    update_row(issue_table, "2", ["2", "人员详情、更新、删除及展示等接口的对象权限校验不完整", "详情接口已有修复记录；更新、删除和展示等接口待整改复核"])
    update_row(issue_table, "3", ["3", "日期配餐查询存在动态条件拼接，异常输入控制不足", "待改为参数化处理并开展异常输入复核"])
    update_row(issue_table, "4", ["4", "部分系统配置存在明文敏感信息管理风险", "待迁移受控配置、轮换相关凭据并核查影响"])
    update_row(issue_table, "5", ["5", "会话令牌保护、有效期控制及账号状态复核需要加强", "待完善令牌和账号状态校验机制"])
    update_row(issue_table, "6", ["6", "现有口令摘要算法和盐值强度不足", "待制定兼容迁移方案并开展登录、改密和重置回归"])

    old_deadline = "我单位将按照回函承诺于2026年9月16日前提交本阶段证明材料。"
    replace_paragraph(
        doc,
        old_deadline,
        "后续按照已提供的安全开发、变更、测试和授权审批要求，对问题整改、测试复核、上线核对和记录归档持续管理；"
        "整改结论以实际形成的代码、测试、发布核对和审批记录为依据。",
    )
    followup_table = find_table(doc, ["后续事项", "工作内容", "当前状态"])
    update_row(
        followup_table,
        "材料整理",
        ["记录归档", "对实际完成的代码整改、测试复核、上线核对和审批情况分类留存", "持续执行"],
    )
    remove_page_break_before(doc, "六、结论及后续安排")
    doc.core_properties.subject = "网络安全限期整改代码审查主体补充材料"
    doc.save(target)


def revise_record() -> None:
    source = SOURCE_DIR / RECORD_NAME
    target = OUTPUT_DIR / RECORD_NAME
    doc = Document(source)

    work_table = find_table(doc, ["序号", "工作事项", "落实情况", "相关材料"])
    update_row(
        work_table,
        "2",
        ["2", "重点代码检查", "已检查登录认证与会话状态、对象权限、输入与查询、敏感配置、令牌保护和口令存储等环节", "问题检查记录"],
        size=9.5,
    )
    compact_table(work_table, size=9.0, line=14)
    update_row(
        work_table,
        "4",
        ["4", "测试与部署核对", "已核对3项测试、10项断言通过，4项失败、1项错误及服务器目标文件检查结果", "测试记录、服务器检查记录"],
        size=9.5,
    )

    heading = find_paragraph(doc, "六、制度执行依据")
    for run in heading.runs:
        run.text = ""
    heading.runs[0].text = "六、已提供的相关管理制度"
    fmt.style_run(heading.runs[0], font=fmt.HEADING_FONT, size=16)
    insert_body_after(
        doc,
        "六、已提供的相关管理制度",
        "下列管理制度文件已在前期材料中提供，本次列明其主要内容及与本项工作的对应关系。制度规定的流程和要求用于约束后续整改、"
        "测试、变更和复核，不据此推定相关执行事项已经全部完成。",
    )
    policy_preface = (
        "下列管理制度文件已在前期材料中提供，本次列明其主要内容及与本项工作的对应关系。制度规定的流程和要求用于约束后续整改、"
        "测试、变更和复核，不据此推定相关执行事项已经全部完成。"
    )
    compact_paragraph(doc, policy_preface, size=12.5, line=20)
    policy_table = find_table(doc, ["序号", "制度名称", "主要管理要求"])
    set_cell_text(policy_table.rows[0].cells[2], "主要管理要求及对应关系", size=10.2)
    update_row(policy_table, "1", ["1", "安全开发管理规定", "规定安全需求、阶段评审、代码检查、问题整改和上线前测试，对应本次问题整改与测试复核"], size=10.2)
    update_row(policy_table, "2", ["2", "组织及岗位职责管理规定", "规定安全管理、开发、测试和运维职责，对应整改、复核和运行维护的职责落实"], size=10.2)
    update_row(policy_table, "3", ["3", "变更管理规定", "规定变更影响分析、测试、回退、验收和记录留存，对应代码变更与上线核对"], size=10.2)
    update_row(policy_table, "4", ["4", "授权和审批管理规定", "规定安全活动、程序变更及相关审批要求，对应整改和发布过程审批"], size=10.2)
    compact_table(policy_table, size=9.2, line=14)

    history_table = find_table(doc, ["事项", "主要内容", "落实情况"])
    compact_table(history_table, size=9.3, line=14)

    old_followup = "后续将逐项完成代码修改、测试复核、上线核对和审批记录整理，并按照回函承诺于2026年9月16日前提交本阶段证明材料。"
    replace_paragraph(
        doc,
        old_followup,
        "后续按照已提供的安全开发、变更、测试和授权审批要求逐项推进整改；对尚未完成的相关测试和受限账号线上对照复测，"
        "在取得实际结果后补充复核结论，并留存代码变更、测试、上线核对和审批记录。",
    )
    compact_paragraph(
        doc,
        "本次已完成管理端后台重点模块代码及相关接口的安全检查，并形成6项问题记录。人员详情接口已有局部修复、定向测试和服务器部署检查结果；"
        "相关测试集中仍有4项失败、1项错误，受限账号线上对照复测尚待完成，其余问题也需继续整改复核。",
        size=12.5,
        line=20,
    )
    compact_paragraph(
        doc,
        "后续按照已提供的安全开发、变更、测试和授权审批要求逐项推进整改；对尚未完成的相关测试和受限账号线上对照复测，"
        "在取得实际结果后补充复核结论，并留存代码变更、测试、上线核对和审批记录。",
        size=12.5,
        line=20,
    )
    compact_last_paragraph(
        doc,
        "北京康比特体育科技股份有限公司\n（盖章）\n2026年9月11日",
        size=13,
        line=21,
        before=8,
    )
    doc.core_properties.subject = "网络安全限期整改代码审查记录主体补充材料"
    doc.save(target)


def main() -> None:
    OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
    revise_report()
    revise_record()
    print(OUTPUT_DIR / REPORT_NAME)
    print(OUTPUT_DIR / RECORD_NAME)


if __name__ == "__main__":
    main()
