#!/usr/bin/env python3
# 遍历 jsons/ 目录下所有 JSON 文件，按 URL 来源匹配 Excel 暗码：
#   - url 含 www.21cnjy.com → 在 "21世纪资料名" 列精确匹配 filename[0]
#   - url 含 www.zxxk.com  → 在 "学科网资料名" 列精确匹配 filename[0]
#       学科网资料名有重复时，从 item 的 url 中提取数字 ID 与各暗码候选值比对消歧
#   - 匹配成功写入 xkw_code，未匹配写入 "0"
#   - 未匹配数据汇总写入 问题数据/未匹配填充0.json

import json       # 用于读写 JSON 文件
import re         # 用于正则表达式提取 URL 中的数字 ID
import glob       # 用于批量匹配目录下的文件路径
import openpyxl   # 用于读取 Excel (.xlsx) 文件

EXCEL_PATH = "结果.xlsx"                      # Excel 数据文件路径
JSON_DIR = "jsons"                             # JSON 文件所在目录
UNMATCHED_OUT = "问题数据/未匹配填充0.json"    # 未匹配数据输出路径


def clean(v):
    # 将单元格值转为字符串并去除首尾空格
    if not v:
        return ""  # 空值直接返回空字符串
    s = str(v).strip()  # 转字符串并去首尾空白
    if s.startswith("'"):
        s = s[1:]  # 去掉 Excel 强制文本时产生的前导单引号（如以 + 开头的单元格）
    return s


def load_excel_indexes(path):
    # 读取 Excel 文件，构建两个查找字典
    # 返回：
    #   map_21:  {21世纪资料名 → 暗码}
    #   map_xkw: {学科网资料名 → [暗码, ...]}  （同名可能对应多个暗码，全部保留）
    wb = openpyxl.load_workbook(path)   # 打开 Excel 文件
    ws = wb.active                       # 取活动工作表
    headers = [cell.value for cell in ws[1]]  # 读取第一行作为列名
    col = {h: i for i, h in enumerate(headers)}  # 构建列名到列索引的映射

    idx_21 = col["21世纪资料名"]   # "21世纪资料名" 列的索引
    idx_xkw = col["学科网资料名"]  # "学科网资料名" 列的索引
    idx_code = col["暗码"]         # "暗码" 列的索引

    map_21 = {}   # 21世纪资料名 → 暗码（唯一映射，重复则后者覆盖前者）
    map_xkw = {}  # 学科网资料名 → [暗码列表]（同名保留全部暗码）
    for row in ws.iter_rows(min_row=2, values_only=True):  # 从第2行开始逐行读取
        code = clean(row[idx_code])       # 读取并清洗暗码
        name_21 = clean(row[idx_21])      # 读取并清洗 21世纪资料名
        name_xkw = clean(row[idx_xkw])    # 读取并清洗学科网资料名
        if name_21 and code:
            map_21[name_21] = code        # 写入 21世纪索引
        if name_xkw and code:
            map_xkw.setdefault(name_xkw, []).append(code)  # 追加到学科网索引列表

    return map_21, map_xkw  # 返回两个索引字典


def extract_url_id(url):
    # 从学科网 URL 中提取纯数字资源 ID
    # 例如 https://www.zxxk.com/soft/38079968.html → '38079968'
    m = re.search(r'/(\d+)(?:\.\w+)?(?:\?|$)', url)  # 匹配路径最后一段的纯数字
    return m.group(1) if m else None  # 有匹配则返回数字字符串，否则返回 None


def resolve_xkw_code(fn0, url, map_xkw):
    # 学科网资料名匹配逻辑：
    #   - 无匹配 → 返回 None
    #   - 只有一个唯一暗码 → 直接返回
    #   - 多个不同暗码（同名不同资源）→ 用 URL ID 消歧，找到匹配则返回，否则返回 None
    codes = map_xkw.get(fn0)  # 查找该文件名对应的暗码列表
    if not codes:
        return None  # 在学科网索引中找不到该文件名
    unique_codes = list(dict.fromkeys(codes))  # 去重并保持原顺序
    if len(unique_codes) == 1:
        return unique_codes[0]  # 唯一暗码，直接返回
    url_id = extract_url_id(url)  # 从 URL 中提取数字 ID
    if url_id and url_id in codes:
        return url_id  # URL ID 与某个暗码一致，用该值
    return None  # 无法消歧，返回 None


def process_file(json_path, map_21, map_xkw):
    # 处理单个 JSON 文件：为每个 item 写入 xkw_code
    with open(json_path, encoding="utf-8") as f:
        data = json.load(f)  # 读取 JSON 数据

    matched = 0         # 成功匹配的计数
    unmatched_items = []  # 未匹配的 item 列表，用于汇总输出

    for item in data.get("items", []):  # 遍历每个资源 item
        fns = item.get("filename", [])  # 读取 filename 列表
        if not fns:
            continue  # filename 为空则跳过该 item
        fn0 = fns[0].strip()        # 只取第一个 filename 并去空格
        url = item.get("url", "")   # 读取该 item 的 URL

        code = None  # 初始化暗码为空
        if "www.21cnjy.com" in url:
            code = map_21.get(fn0)  # 在 21世纪索引中精确查找
        elif "www.zxxk.com" in url:
            code = resolve_xkw_code(fn0, url, map_xkw)  # 在学科网索引中查找（含消歧）

        if code:
            item["xkw_code"] = code  # 匹配成功，写入暗码
            matched += 1             # 匹配计数加一
        else:
            item["xkw_code"] = "0"   # 未匹配，填充 "0"
            unmatched_items.append({
                "source_file": json_path,  # 来源 JSON 文件路径
                "filename": fn0,           # 未匹配的文件名
                "url": url,                # 对应的 URL
            })

    with open(json_path, "w", encoding="utf-8") as f:
        json.dump(data, f, ensure_ascii=False, indent=2)  # 将修改后的数据写回 JSON 文件

    return matched, unmatched_items  # 返回匹配数和未匹配列表


def main():
    print("加载 Excel 索引...")
    map_21, map_xkw = load_excel_indexes(EXCEL_PATH)  # 构建 Excel 查找索引
    print(f"Excel 索引：21世纪 {len(map_21)} 条，学科网 {len(map_xkw)} 个不重复名称\n")

    files = sorted(glob.glob(f"{JSON_DIR}/*.json"))  # 获取所有 JSON 文件路径并排序
    total_files = len(files)    # JSON 文件总数
    total_matched = 0           # 全局匹配计数
    all_unmatched = []          # 全局未匹配列表

    for i, path in enumerate(files, 1):  # 逐个处理每个 JSON 文件
        matched, unmatched_items = process_file(path, map_21, map_xkw)  # 处理单文件
        total_matched += matched                     # 累加匹配数
        all_unmatched.extend(unmatched_items)        # 合并未匹配列表
        print(f"[{i}/{total_files}] {path}  匹配:{matched}  未匹配:{len(unmatched_items)}")

    with open(UNMATCHED_OUT, "w", encoding="utf-8") as f:
        json.dump(all_unmatched, f, ensure_ascii=False, indent=2)  # 写出所有未匹配数据

    total_unmatched = len(all_unmatched)  # 全局未匹配总数
    print(f"\n完成：处理 {total_files} 个文件，写入 xkw_code {total_matched + total_unmatched} 条"
          f"（匹配:{total_matched}，填0:{total_unmatched}）")
    print(f"未匹配数据已保存至 {UNMATCHED_OUT}")  # 提示未匹配数据保存路径


if __name__ == "__main__":
    main()  # 脚本直接运行时执行 main 函数
