# -*- coding: utf-8 -*-
"""
生成报销表_zh.py
=================
一键生成「自动算金额的报销表 .xlsx」。
配套教程：B1 《AI 帮你 10 分钟做完一张报销表》（中文版）。

本脚本对应教程中的关键三件事：
  1) 用 openpyxl 把数据写成一张 Excel 工作表；
  2) 把数据区域转成"超级表"（Excel Table，Ctrl+T 的等价物）；
  3) 给超级表加上"总计行"，对"金额"列自动求和（再多加一行也会跟着算）。

运行前提（一次就好）：
  pip install openpyxl

最简单的用法（默认参数）：
  python 生成报销表_zh.py

指定输入输出：
  python 生成报销表_zh.py --input 示例数据_sample.csv --output 报销表_2026Q3.xlsx
"""
# ↑↑↑ ↑↑↑ ↑↑↑ ↑↑↑ ↑↑↑
# 中文：本脚本使用 UTF-8 编码保存
# English: this script is saved in UTF-8 encoding

import argparse  # 命令行参数解析 / command-line argument parsing
import csv        # 读取示例 CSV / reading sample CSV
import sys        # 退出码 / exit codes
from pathlib import Path  # 路径处理 / path handling

try:
    # 导入 openpyxl 的核心对象 / core objects
    from openpyxl import Workbook                                  # 新建工作簿 / new workbook
    from openpyxl.styles import Font, PatternFill, Alignment, Border, Side  # 样式 / styling
    from openpyxl.worksheet.table import Table, TableStyleInfo     # 超级表 / Excel Table
except ImportError:  # 没装 openpyxl 就给出友好提示 / friendly hint if not installed
    sys.stderr.write(
        "[错误] 没找到 openpyxl，请先运行：pip install openpyxl\n"
        "[Error] openpyxl not found. Run: pip install openpyxl\n"
    )
    sys.exit(1)


# ============================================================
# 配置区 / Configuration
# ============================================================

# 字段定义：列名 -> 列宽
# Field definitions: column name -> column width
FIELDS = [
    ("序号",        8),   # Serial No.
    ("日期",        14),  # Date
    ("类别",        12),  # Category
    ("金额",        12),  # Amount
    ("说明",        36),  # Description
    ("发票号",      22),  # Invoice No.
    ("报销人",      12),  # Submitter
]

# 金额列所在的列号（从 1 开始）。总计行将对这一列求和。
# Column index (1-based) where the Amount lives. The total row will sum this column.
AMOUNT_COL_INDEX = 4  # 字段定义里"金额"是第 4 项 / Amount is the 4th item above

# 超级表的"显示名"，在 Excel 里会出现在"表格工具"中
# Display name of the Excel Table (shows up in Table Tools)
TABLE_NAME = "ExpenseSheet"

# 表格样式（内置样式名）。可换成 TableStyleLight1 ~ TableStyleDark11
# Built-in table style name. Pick from TableStyleLight1 ~ TableStyleDark11
TABLE_STYLE = "TableStyleMedium2"

# 默认文件路径 / Default paths
DEFAULT_INPUT = Path(__file__).parent / "示例数据_sample.csv"   # 默认读取本目录的样例 CSV
DEFAULT_OUTPUT = Path(__file__).parent / "报销表.xlsx"          # 默认输出到本目录


# ============================================================
# 读取示例数据 / Read sample data
# ============================================================

def read_rows(csv_path: Path):
    """
    读取 CSV 文件，返回 list[dict]，每行一个字典（键是字段名）。
    Read CSV and return list[dict], one dict per row.
    """
    # 打开 CSV，UTF-8 编码（Windows 下也安全）
    # Open CSV with UTF-8 (also safe on Windows)
    with csv_path.open("r", encoding="utf-8-sig", newline="") as f:
        reader = csv.DictReader(f)
        rows = [dict(r) for r in reader]  # 全部读出来 / read all
    return rows


# ============================================================
# 写入工作表 / Write worksheet
# ============================================================

def write_rows(ws, rows):
    """
    把 list[dict] 写入工作表，第一行表头，第二行起数据。
    Write list[dict] to worksheet, header on row 1, data from row 2.
    """
    # ---- 表头 / Header ----
    header = [name for name, _ in FIELDS]  # 取字段名 / take field names
    for col_idx, name in enumerate(header, start=1):
        cell = ws.cell(row=1, column=col_idx, value=name)
        # 表头加粗、白字、深灰底 / bold white text on dark fill
        cell.font = Font(bold=True, color="FFFFFF")
        cell.fill = PatternFill("solid", fgColor="305496")
        cell.alignment = Alignment(horizontal="center", vertical="center")

    # ---- 数据 / Data ----
    for r_offset, row in enumerate(rows, start=2):  # 从第 2 行开始 / start from row 2
        for c_idx, (name, _) in enumerate(FIELDS, start=1):
            value = row.get(name, "")  # 取值，没有就填空字符串 / empty string if missing
            ws.cell(row=r_offset, column=c_idx, value=value)

    # ---- 列宽 / Column widths ----
    for c_idx, (_, width) in enumerate(FIELDS, start=1):
        ws.column_dimensions[ws.cell(row=1, column=c_idx).column_letter].width = width


def format_money(ws, last_data_row):
    """
    给金额列设"货币"格式，避免出现 553 / ¥553.00 两样。
    Apply Currency format to the Amount column.
    """
    amount_col_letter = ws.cell(row=2, column=AMOUNT_COL_INDEX).column_letter
    # 选整列：表头到数据最后一行 / header through last data row
    for r in range(2, last_data_row + 1):
        ws[f"{amount_col_letter}{r}"].number_format = '"¥"#,##0.00'


# ============================================================
# 套"超级表" + 总计行 / Apply Excel Table + Total Row
# ============================================================

def apply_table(ws, last_data_row):
    """
    把数据区域转成超级表，并启用"总计行"，对金额列自动求和。
    Convert data range to an Excel Table and enable Total Row summing Amount.
    """
    last_col_letter = ws.cell(row=1, column=len(FIELDS)).column_letter
    ref = f"A1:{last_col_letter}{last_data_row}"  # 表格引用范围 / table reference

    # 创建超级表对象 / create Table object
    tbl = Table(displayName=TABLE_NAME, ref=ref)

    # 表格样式：中等条纹 / medium style with row stripes
    tbl.tableStyleInfo = TableStyleInfo(
        name=TABLE_STYLE,
        showFirstColumn=False,
        showLastColumn=False,
        showRowStripes=True,    # 隔行底色 / row stripes
        showColumnStripes=False,
    )

    # ---- 关键：开启总计行 ----
    # ---- KEY: turn on the Total Row ----
    tbl.totalsRowShown = True   # 在表末尾多显示一行 / show a totals row
    tbl.totalsRowCount = 1      # 总计行就一行 / just one totals row

    # 每列都需要配置一个 totals 函数（None 表示该列不出现在总计行公式中）
    # Each column needs a totals function (None = leave as label).
    field_names = [name for name, _ in FIELDS]
    totals_function = []
    totals_label = []
    for name in field_names:
        if name == "金额":
            totals_function.append("sum")     # 金额列用 SUM 公式
            totals_label.append("总计/Total")  # 这一列显示的文字
        else:
            totals_function.append("custom")   # 用自定义内容（占位 None 也可以）
            totals_label.append("")            # 空字符串占位 / empty placeholder

    # openpyxl 要求 totalsRowFunction / totalsRowLabel 与字段顺序一一对应
    # The order must match the column order.
    tbl.totalsRowFunction = totals_function
    tbl.totalsRowLabel = totals_label

    # ---- 真正把表加到工作表 ----
    # ---- Actually add the table to the worksheet ----
    ws.add_table(tbl)


# ============================================================
# 主流程 / Main flow
# ============================================================

def build_expense_sheet(input_path: Path, output_path: Path):
    """
    主流程：读取 CSV → 写表 → 套超级表 + 总计行 → 保存。
    Main flow: read CSV → write sheet → apply Table+Total Row → save.
    """
    rows = read_rows(input_path)
    if not rows:
        # 没有数据也要提示 / friendly message when CSV is empty
        sys.stderr.write(f"[警告] CSV 是空的：{input_path}\n[Warning] CSV is empty: {input_path}\n")
        return False

    wb = Workbook()
    ws = wb.active
    ws.title = "报销表"  # 工作表标签名 / sheet tab name

    write_rows(ws, rows)                                # 写表头和数据
    format_money(ws, last_data_row=1 + len(rows))        # 金额列设货币格式
    apply_table(ws, last_data_row=1 + len(rows))         # 套超级表 + 总计行

    # ---- 保存 ----
    # ---- Save ----
    output_path.parent.mkdir(parents=True, exist_ok=True)  # 确保输出目录存在
    wb.save(output_path)

    print(f"[完成/Done] 生成 {len(rows)} 行报销数据 → {output_path}")
    print(f"[下一步/Next] 用 Excel 打开 {output_path.name}，金额列已设货币，")
    print(f"          末尾的「总计/Total」行会自动对金额列求和。")
    print(f"          (Open {output_path.name} in Excel; the Amount column uses")
    print(f"           currency format and the Total Row sums it automatically.)")
    return True


def parse_args():
    """命令行参数 / command-line arguments"""
    p = argparse.ArgumentParser(
        description="一键生成带「总计行」的报销表 .xlsx / Generate an expense .xlsx with a Total Row"
    )
    p.add_argument("--input",  "-i", type=Path, default=DEFAULT_INPUT,  help="输入 CSV / input CSV")
    p.add_argument("--output", "-o", type=Path, default=DEFAULT_OUTPUT, help="输出 xlsx / output xlsx")
    return p.parse_args()


def main():
    args = parse_args()
    if not args.input.exists():
        sys.stderr.write(f"[错误] 找不到输入文件：{args.input}\n")
        sys.exit(2)
    ok = build_expense_sheet(args.input, args.output)
    sys.exit(0 if ok else 3)


if __name__ == "__main__":
    main()