#  Copyright (c) 2025-2026  Jeffrey Hsu - JITToolBox
#  #
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#  #
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#  #
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <https://www.gnu.org/licenses/>.

import io
import os
import traceback
from typing import Callable

from docx import Document
from docx.enum.section import WD_ORIENT
from docx.enum.table import WD_ALIGN_VERTICAL, WD_ROW_HEIGHT_RULE
from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_LINE_SPACING
from docx.oxml import OxmlElement
from docx.oxml.ns import qn
from docx.shared import Pt, Cm
from docx.text.run import Run

from module import LOGLEVEL
from module.achievement.excel import ExcelReader


class DocxWriter:
    full_file_path: str

    def __init__(self, save_path: str, filename: str, excel_reader: ExcelReader,
                 signal: Callable[[str, str], None] = lambda x, y: print(x)):
        super().__init__()
        self.save_path = save_path
        self.filename = filename
        self.excel_reader = excel_reader
        self.full_file_path = os.path.join(save_path, filename) + '.docx'
        self.signal = signal

    def write(self) -> int:
        try:
            if not os.path.exists(self.save_path):
                os.makedirs(self.save_path)
            if os.path.exists(self.full_file_path):
                self.signal(f"文件'{self.filename}'已存在，将覆盖原文件", LOGLEVEL.WARNING)
            doc = Document()
            for section in doc.sections:
                new_width, new_height = Cm(29.7), Cm(21)  # A4纸张
                # 设置纸张方向为横向
                section.orientation = WD_ORIENT.LANDSCAPE

                # 设置页边距（例如：上下左右均为2厘米）
                section.top_margin = Cm(2)
                section.bottom_margin = Cm(2)
                section.left_margin = Cm(2)
                section.right_margin = Cm(1)

                section.page_width = new_width
                section.page_height = new_height

            paragraph = doc.add_paragraph()
            # 设置段落居中对齐
            paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
            run = paragraph.add_run(f"《{self.excel_reader.course_name}》课程目标达成情况评价报告")
            self.set_run_font(run, 16, '黑体', '黑体', True)

            paragraph = doc.add_paragraph()
            run = paragraph.add_run(
                f"1.班级{'（年级）' if len(self.excel_reader.class_list) == 1 else ''}课程目标达成情况分析")
            self.set_run_font(run, 14, 'Times New Roman', '黑体', True)

            # 班级课程目标达成情况分析表
            class_info_str = "班级：{}     学生人数：{}                                                                                  任课教师：{}"
            for r_index in range(len(self.excel_reader.class_list)):
                self.signal(f"正在生成 {self.excel_reader.class_list[r_index]} 表格", LOGLEVEL.INFO)
                paragraph = doc.add_paragraph()
                # 设置段落为分散对齐
                paragraph.alignment = WD_ALIGN_PARAGRAPH.DISTRIBUTE
                run = paragraph.add_run(class_info_str.format(
                    self.excel_reader.class_list[r_index],
                    self.excel_reader.class_number[r_index],
                    self.excel_reader.course_teacher_name
                ))
                self.set_run_font(run, 10.5, 'Times New Roman', '宋体')

                if len(self.excel_reader.class_list) == 1:
                    rows = 14 + self.excel_reader.class_number[r_index] + self.excel_reader.kpi_number
                else:
                    rows = 12 + self.excel_reader.class_number[r_index] + self.excel_reader.kpi_number
                cols = 2 + self.calculate_columns(self.excel_reader.achievement_level[r_index])  # 加上第一列

                table = doc.add_table(rows=rows, cols=cols)

                # 设置外侧框线粗1.5磅，内侧框线粗0.5磅
                self.set_table_borders(table)

                # 合并前六行头两个单元格
                for row in range(6):
                    cell_start = table.cell(row, 0)
                    cell_end = table.cell(row, 1)
                    cell_start.merge(cell_end)
                # 合并前三行的单元格
                for row in range(3):
                    col_span = 2  # 从第三列开始
                    for performance in self.excel_reader.achievement_level[r_index]:
                        non_none_count = 3 - performance.scores.count(None)
                        if non_none_count > 1:
                            cell_start = table.cell(row, col_span)
                            cell_end = table.cell(row, col_span + non_none_count - 1)
                            cell_start.merge(cell_end)
                        col_span += non_none_count

                X = self.excel_reader.kpi_number + 5  # X的值
                if len(self.excel_reader.class_list) == 1:
                    X += 2

                # 第一、二行合并第二个单元格
                for row in range(rows - X, rows - X + 2):
                    table.cell(row, 0).merge(table.cell(row, 1))

                # 第三行的合并操作
                # 第一个单元格与第三+len(a[0])-1行第二个单元格合并
                table.cell(rows - X + 2, 0).merge(table.cell(rows - X + 2 + self.excel_reader.kpi_number - 1, 1))
                for i in range(self.excel_reader.kpi_number):
                    # 第三个与第四个单元格合并
                    table.cell(rows - X + 2 + i, 2).merge(table.cell(rows - X + i + 2, 3))
                    # 从第五个单元格合并到最后
                    start_cell = table.cell(rows - X + 2 + i, 4)
                    end_cell = table.cell(rows - X + 2 + i, cols - 1)
                    start_cell.merge(end_cell)

                # 第三+len(a[0])行的合并操作
                # 合并第一个到第二个单元格
                table.cell(rows - X + 2 + self.excel_reader.kpi_number, 0).merge(
                    table.cell(rows - X + 2 + self.excel_reader.kpi_number, 1))
                # 合并第三个到最后一个单元格
                start_cell = table.cell(rows - X + 2 + self.excel_reader.kpi_number, 2)
                end_cell = table.cell(rows - X + 2 + self.excel_reader.kpi_number, cols - 1)
                start_cell.merge(end_cell)

                if len(self.excel_reader.class_list) == 1:
                    # 合并前两个单元格
                    for i in range(2):
                        start = rows - X + 3 + i + self.excel_reader.kpi_number
                        table.cell(start, 0).merge(table.cell(start, 1))
                        # 依次合并后面的单元格
                        for row in range(start, start + self.excel_reader.kpi_number):
                            col_span = 2  # 从第三列开始
                            for performance in self.excel_reader.achievement_level[r_index]:
                                non_none_count = 3 - performance.scores.count(None)
                                if non_none_count > 1:
                                    try:
                                        cell_start = table.cell(row, col_span)
                                        cell_end = table.cell(row, col_span + non_none_count - 1)
                                        cell_start.merge(cell_end)
                                    except IndexError:
                                        pass
                                        # self.signal(f"单元格合并失败：({row}, {col_span})，需要自行检查表格准确性",
                                        #            LOGLEVEL.WARNING)
                                    col_span += non_none_count

                start = rows - X + 3 + self.excel_reader.kpi_number
                if len(self.excel_reader.class_list) == 1:
                    start += 2
                end = rows
                for row in range(start, end):
                    start_cell = table.cell(row, 1)
                    end_cell = table.cell(row, cols - 1)
                    start_cell.merge(end_cell)

                # 填充表格数据
                self.put_data_to_table(table, self.excel_reader.get_word_template, r_index)

                doc.add_page_break()

            if len(self.excel_reader.class_list) > 1:
                paragraph = doc.add_paragraph()
                run = paragraph.add_run("2.年级课程目标达成情况分析")
                self.set_run_font(run, 14, 'Times New Roman', '黑体', True)

                # 2.年级课程目标达成情况分析
                class_info_str = "班级：{}                                                                                           课程负责人：{}"
                paragraph = doc.add_paragraph()
                paragraph.alignment = WD_ALIGN_PARAGRAPH.DISTRIBUTE
                run = paragraph.add_run(class_info_str.format(
                    self.excel_reader.total_class_str,
                    self.excel_reader.course_lead_teacher_name
                ))
                self.set_run_font(run, 10.5, "Times New Roman", '宋体')

                rows = 10 + len(self.excel_reader.class_list)
                cols = 2 + self.calculate_columns(self.excel_reader.achievement_level[0])  # 加上第一列

                table = doc.add_table(rows=rows, cols=cols)

                # 设置外侧框线粗1.5磅，内侧框线粗0.5磅
                self.set_table_borders(table)

                # 合并前六行头两个单元格
                for row in range(rows - 2):
                    cell_start = table.cell(row, 0)
                    cell_end = table.cell(row, 1)
                    cell_start.merge(cell_end)
                # 合并前三行的单元格
                for row in range(3):
                    col_span = 2  # 从第三列开始
                    for performance in self.excel_reader.achievement_level[0]:
                        non_none_count = 3 - performance.scores.count(None)
                        if non_none_count > 1:
                            cell_start = table.cell(row, col_span)
                            cell_end = table.cell(row, col_span + non_none_count - 1)
                            cell_start.merge(cell_end)
                        col_span += non_none_count
                # 合并中间X行的单元格
                for row in range(6, 8 + len(self.excel_reader.class_list)):
                    col_span = 2
                    for performance in self.excel_reader.achievement_level[0]:
                        non_none_count = 3 - performance.scores.count(None)
                        if non_none_count > 1:
                            cell_start = table.cell(row, col_span)
                            cell_end = table.cell(row, col_span + non_none_count - 1)
                            cell_start.merge(cell_end)
                        col_span += non_none_count
                # 合并最后两行的单元格
                for row in range(2):
                    cell_start = table.cell(rows - row - 1, 1)
                    cell_end = table.cell(rows - row - 1, cols - 1)
                    cell_start.merge(cell_end)

                # 填充数据
                self.put_data_to_table(table, self.excel_reader.get_word_template_part_2)

                doc.add_page_break()

            # 3. 课程目标达成情况的合理性评价
            paragraph = doc.add_paragraph()
            run = paragraph.add_run(f"{3 if len(self.excel_reader.class_list) > 1 else 2}"
                                    f". 课程目标达成情况的合理性评价")
            self.set_run_font(run, 14, 'Times New Roman', '黑体', True)

            rows = 11
            cols = 6
            table = doc.add_table(rows=rows, cols=cols)
            # 设置外侧框线粗1.5磅，内侧框线粗0.5磅
            self.set_table_borders(table)

            # 合并第一行
            cell_start = table.cell(0, 0)
            cell_end = table.cell(0, cols - 1)
            cell_start.merge(cell_end)
            # 合并第二行至最后
            for i in range(1, rows):
                match i:
                    case 2:
                        table.cell(i, 2).merge(table.cell(i, 3))
                        table.cell(i, 4).merge(table.cell(i, 5))
                        table.cell(i, 1).width = Cm(7.42)
                        table.cell(i, 2).width = Cm(7.42)
                        table.cell(i, 4).width = Cm(7.41)
                    case 8 | 10:
                        table.cell(i - 1, 0).merge(table.cell(i, 0))
                        table.cell(i, 1).width = Cm(11.23)
                        table.cell(i, 2).width = Cm(1.48)
                        table.cell(i, 3).width = Cm(3.4)
                        table.cell(i, 4).width = Cm(1.39)
                    case _:
                        cell_start = table.cell(i, 1)
                        cell_end = table.cell(i, cols - 1)
                        cell_start.merge(cell_end)
                        # 填充数据
            self.put_data_to_table(table, self.excel_reader.get_word_template_part_3)

            # 应用样式
            self.signal("正在应用文字样式", LOGLEVEL.INFO)
            # 遍历文档中的所有段落
            for index, paragraph in enumerate(doc.paragraphs):
                if index == 0:
                    self.set_paragraph_format(paragraph, WD_LINE_SPACING.ONE_POINT_FIVE)
                else:
                    self.set_paragraph_format(paragraph)

            # 遍历文档中的所有表格及其单元格
            t_len = len(doc.tables)
            part_1_table_index = [x for x in range(len(self.excel_reader.class_list))]
            part_2_table_index = [len(part_1_table_index)] if len(self.excel_reader.class_list) != 1 else []
            part_3_table_index = [t_len - 1]

            for t_index, table in enumerate(doc.tables):
                self.set_table_borders(table)
                # part_3_table_index 表格第9和11行（索引8和10）特殊边框处理
                if t_index in part_3_table_index:
                    for r_idx in [8, 10]:
                        row = table.rows[r_idx]
                        prev_row = table.rows[r_idx - 1]
                        # 上一行（第8行和第10行，索引7和9）第2-6列移除下边框
                        for c_idx in range(1, 6):
                            self.set_cell_border(prev_row.cells[c_idx], bottom=0)
                        # 第2列（索引1）：没有上边框和右边框
                        self.set_cell_border(row.cells[1], top=0, right=0)
                        # 第3-5列（索引2-4）：没有上边框和左右边框
                        for c_idx in [2, 3, 4]:
                            self.set_cell_border(row.cells[c_idx], top=0, left=0, right=0)
                        # 第6列（索引5）：没有上边框和左边框
                        self.set_cell_border(row.cells[5], top=0, left=0)
                    # 插入签名图片
                    if self.excel_reader.major_director_signature_image is not None:
                        self.insert_pil_image(table.cell(8, 3),
                                              self.excel_reader.major_director_signature_image,
                                              height=Cm(1.2))
                    if self.excel_reader.course_leader_signature_image is not None:
                        self.insert_pil_image(table.cell(10, 3),
                                              self.excel_reader.course_leader_signature_image,
                                              height=Cm(1.2))
                for r_index, row in enumerate(table.rows):
                    row.height_rule = WD_ROW_HEIGHT_RULE.AT_LEAST
                    # part_3_table_index 表格第9和11行（索引8和10）行高为1.2cm
                    if t_index in part_3_table_index and r_index in [8, 10]:
                        row.height = Cm(1.2)
                    else:
                        row.height = Cm(0.7)
                    for c_index, cell in enumerate(row.cells):
                        cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER
                        self.set_cell_margins(cell, start=57, end=57)
                        for paragraph in cell.paragraphs:
                            for run in paragraph.runs:
                                self.set_run_font(run, 10.5, 'Times New Roman', '宋体')
                            self.set_paragraph_format(paragraph)
                            special_cell = []
                            if t_index in part_1_table_index:
                                ll = [(9 + x + self.excel_reader.class_number[t_index], 4) for x in
                                      range(self.excel_reader.kpi_number)]
                                start_line = 9 + self.excel_reader.kpi_number
                                special_cell.extend(ll)
                                special_cell.extend([
                                    (1, 2),
                                    (2, 2),
                                    (start_line + self.excel_reader.class_number[t_index], 2),
                                ])
                                if len(self.excel_reader.class_list) == 1:
                                    special_cell.extend([
                                        (start_line + 3 + self.excel_reader.class_number[t_index], 1),
                                        (start_line + 4 + self.excel_reader.class_number[t_index], 1),
                                    ])
                                    lst = [(start_line + 3 + self.excel_reader.class_number[t_index], 1)]
                                else:
                                    special_cell.extend([
                                        (start_line + 1 + self.excel_reader.class_number[t_index], 1),
                                        (start_line + 2 + self.excel_reader.class_number[t_index], 1),
                                    ])
                                    lst = [(start_line + 1 + self.excel_reader.class_number[t_index], 1)]
                                if (r_index, c_index) in lst:
                                    self.insert_image_from_data(cell,
                                                                self.excel_reader.pic_list[t_index],
                                                                Cm(7 * self.excel_reader.kpi_number)
                                                                )
                                if (len(self.excel_reader.class_list) == 1 and
                                        r_index == start_line + 2 + self.excel_reader.class_number[t_index]):
                                    self.change_font_to_wingding2(paragraph)
                            elif t_index in part_2_table_index:
                                special_cell = [
                                    (1, 2),
                                    (2, 2),
                                    (8 + len(self.excel_reader.class_list), 1),
                                    (9 + len(self.excel_reader.class_list), 1),
                                ]
                                if r_index == 7 + len(self.excel_reader.class_list):
                                    self.change_font_to_wingding2(paragraph)
                            elif t_index in part_3_table_index:
                                special_cell = [
                                    (1, 1),
                                    (2, 1),
                                    (3, 1),
                                    (4, 1),
                                    (5, 1),
                                    (6, 1),
                                    (7, 1),
                                    (8, 1),
                                    (9, 1),
                                    (10, 1),
                                ]
                                if r_index == 0:
                                    for run in paragraph.runs:
                                        self.set_run_font(run, 10.5, 'Times New Roman', '宋体', True)
                                else:
                                    self.change_font_to_wingding2(paragraph)
                            if any(r_index == pair[0] and c_index >= pair[1] for pair in special_cell):
                                paragraph.alignment = WD_ALIGN_PARAGRAPH.LEFT
                            else:
                                paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER

            doc.save(self.full_file_path)

        except PermissionError as e:
            raise Exception(f"""
            简要错误信息：{str(e)}
            这可能是由于Word文件被占用所导致的，请关闭相应文件后再试。
            """)
        except Exception as _:
            error_message = traceback.format_exc()
            raise Exception(f"""
            原始错误信息：
            {error_message}
            """)

    def set_save_path(self, save_path: str):
        self.save_path = save_path
        self.full_file_path = os.path.join(save_path, self.filename)

    def set_filename(self, filename: str):
        self.filename = filename
        self.full_file_path = os.path.join(self.save_path, filename)

    def set_run_font(self, run: Run, size, western_font, chinese_font, bold=False):
        run.font.size = Pt(size)
        run.font.name = western_font
        run.bold = bold
        run._element.rPr.rFonts.set(qn('w:eastAsia'), chinese_font)

    def set_cell_border(self, cell, **kwargs):
        """
        设置单元格边框
        kwargs: top, bottom, left, right, inside_h, inside_v
        值为0时移除边框，值大于0时设置边框粗细
        """
        tc = cell._tc
        tcPr = tc.get_or_add_tcPr()

        # 创建一个新的'TC边框'元素
        tcBorders = OxmlElement('w:tcBorders')

        # 对于每个边框方向
        for key, value in kwargs.items():
            if value is not None:
                tag = 'w:{}'.format(key)
                border = OxmlElement(tag)
                if value == 0:
                    # 移除边框
                    border.set(qn('w:val'), 'nil')
                else:
                    border.set(qn('w:val'), 'single')
                    border.set(qn('w:sz'), str(int(value * 8)))
                    border.set(qn('w:space'), '0')
                    border.set(qn('w:color'), 'auto')
                tcBorders.append(border)

        # 将边框添加到单元格属性中
        tcPr.append(tcBorders)

    def set_paragraph_format(self, paragraph, line_spacing_rule: WD_LINE_SPACING = WD_LINE_SPACING.SINGLE):
        paragraph_format = paragraph.paragraph_format
        paragraph_format.line_spacing_rule = line_spacing_rule  # 单倍行距
        paragraph_format.space_before = Pt(0)  # 段前间距
        paragraph_format.space_after = Pt(0)  # 段后间距

    def calculate_columns(self, performance_list):
        """
        计算表格的列数
        :param performance_list:
        :return:
        """
        col_count = 0
        for performance in performance_list:
            col_count += 3 - performance.scores.count(None)  # 非None值的数量
        return col_count

    def put_data_to_table(self, table, gen_func, *args):
        """填充表格数据"""
        data = gen_func(*args)
        for i, row in enumerate(table.rows):
            for j, cell in enumerate(row.cells):
                if cell.text == "":
                    # 填充数据
                    # 这里需要根据您的数据结构来调整
                    d = next(data)
                    cell.text = str(d)

    def set_cell_margins(self, cell, **kwargs):
        """
        设置单元格的边距。
        cell: 要修改的单元格实例
        kwargs: 边距值，以twips为单位（1/1440英寸）
        示例：set_cell_margins(cell, top=50, start=50, bottom=50, end=50)
        """
        tc = cell._tc
        tcPr = tc.get_or_add_tcPr()
        tcMar = OxmlElement('w:tcMar')

        for m in ["top", "start", "bottom", "end"]:
            if m in kwargs:
                node = OxmlElement("w:{}".format(m))
                node.set(qn('w:w'), str(kwargs.get(m)))
                node.set(qn('w:type'), 'dxa')
                tcMar.append(node)

        tcPr.append(tcMar)

    def is_start_of_merged_cells(self, cell):
        """ 检查单元格是否是合并单元格的起始单元格 """
        merge = cell._element.xpath('./w:tcPr/w:vMerge')
        if merge:
            return merge[0].get(qn('w:val')) == 'restart'
        return False

    def is_merged_vertically(self, cell):
        """ 检查单元格是否垂直合并 """
        merge = cell._element.xpath('./w:tcPr/w:vMerge')
        return bool(merge)

    def set_table_borders(self, table, outside_sz=1.5, inside_sz=0.5):
        """
        设置整个表格的边框样式
        outside_sz: 外侧边框大小
        inside_sz: 内部分割线大小
        """
        for row in table.rows:
            for cell in row.cells:
                self.set_cell_border(cell, top=inside_sz, bottom=inside_sz, left=inside_sz, right=inside_sz)

        # 设置外侧边框
        for cell in table.rows[0].cells:
            self.set_cell_border(cell, top=outside_sz)
        for cell in table.rows[-1].cells:
            self.set_cell_border(cell, bottom=outside_sz)
        for row in table.rows:
            self.set_cell_border(row.cells[0], left=outside_sz)
            self.set_cell_border(row.cells[-1], right=outside_sz)

    def insert_image_from_data(self, cell, image_data, width=Cm(21), height=Cm(4.5)):
        # 创建一个临时文件
        with io.BytesIO(image_data) as image_stream:
            image_stream.seek(0)
            # 插入图片
            # 在单元格中添加一个段落
            paragraph = cell.paragraphs[0]
            # 在段落中插入图片
            run = paragraph.add_run()
            run.add_picture(image_stream, width=width, height=height)

    def insert_pil_image(self, cell, pil_image, width=None, height=Cm(4.5)):
        """插入PIL Image对象到单元格"""
        image_stream = io.BytesIO()
        pil_image.save(image_stream, format='PNG')
        image_stream.seek(0)
        paragraph = cell.paragraphs[0]
        run = paragraph.add_run()
        run.add_picture(image_stream, width=width, height=height)

    def is_chinese(self, char):
        """判断字符是否为中文"""
        if '\u4e00' <= char <= '\u9fff':
            return True
        return False

    def change_font_to_wingding2(self, paragraph):
        for run in paragraph.runs:
            if not run.text.strip():  # 跳过不包含文本的runs
                continue
            new_text = ""
            for char in run.text:
                if char in ['R', '£']:
                    # 对于特殊字符，创建新的run
                    if new_text:
                        new_run = paragraph.add_run(new_text)
                        self.set_run_font(new_run, 10.5, 'Times New Roman', '宋体')
                        new_text = ""
                    special_run = paragraph.add_run(char)
                    self.set_run_font(special_run, 10.5, 'Wingdings 2', '宋体')
                else:
                    # 累积其他字符
                    new_text += char
            if new_text:
                # 创建剩余文本的run
                new_run = paragraph.add_run(new_text)
                self.set_run_font(new_run, 10.5, 'Times New Roman', '宋体')
            run.clear()
