#!/usr/bin/env python3
"""
森芒 Kodi 插件仓库 - addons.xml 自动生成脚本

用法:
    python3 generate_senmang.py

扫描服务器插件目录，提取每个 zip 中的 addon.xml，
合并为 addons.xml + addons.xml.md5，同时生成仓库安装包 zip。
"""

import os
import hashlib
import zipfile
import re
import xml.etree.ElementTree as ET

# ============ 配置区 ============

# 服务器插件目录（脚本所在目录即服务器根目录）
SERVER_DIR = os.path.dirname(os.path.abspath(__file__))

# 仓库 addon.xml 路径（本地工作区）
REPO_ADDON_XML = os.path.join(os.path.dirname(os.path.abspath(__file__)), "repository.senmang", "addon.xml")

# ================================


def version_key(version_str):
    """将版本号转为可比较的元组，如 1.10.3 -> (1, 10, 3)"""
    parts = re.split(r'[^0-9]+', version_str)
    try:
        return tuple(int(p) for p in parts if p)
    except ValueError:
        return (0,)


def extract_assets_from_zip(zip_path, dest_dir):
    """从 zip 中提取 icon 和 fanart 到目标目录（支持根目录和 resources/ 子目录）"""
    try:
        with zipfile.ZipFile(zip_path, 'r') as zf:
            namelist = zf.namelist()
            prefix = ""
            for name in namelist:
                if name.endswith('addon.xml') and name.count('/') == 1:
                    prefix = name[:-9]
                    break
            if not prefix:
                return

            # 从 addon.xml 读取 assets 里声明的 icon/fanart 路径
            declared_assets = []
            try:
                addon_content = zf.read(prefix + "addon.xml").decode('utf-8')
                assets_match = re.search(r'<assets>(.*?)</assets>', addon_content, re.DOTALL)
                if assets_match:
                    tags = re.findall(r'<(icon|fanart)>(.*?)</>', assets_match.group(1))
                    for tag, path in tags:
                        if path.strip() and not path.strip().startswith('http'):
                            declared_assets.append(path.strip())
            except Exception:
                pass

            extracted = set()

            # 先尝试 addon.xml 声明的路径
            for asset_path in declared_assets:
                full_path = prefix + asset_path
                dest_name = os.path.basename(asset_path)
                if full_path in namelist and dest_name not in extracted:
                    dest = os.path.join(dest_dir, dest_name)
                    with zf.open(full_path) as src, open(dest, 'wb') as dst:
                        dst.write(src.read())
                    print(f"    提取: {dest_name}")
                    extracted.add(dest_name)

            # 再尝试常见路径
            common_assets = ['icon.png', 'icon.jpg', 'fanart.jpg', 'fanart.png']
            for asset in common_assets:
                if asset in extracted:
                    continue
                for sub in ['', 'resources/']:
                    asset_path = prefix + sub + asset
                    if asset_path in namelist:
                        dest = os.path.join(dest_dir, asset)
                        with zf.open(asset_path) as src, open(dest, 'wb') as dst:
                            dst.write(src.read())
                        print(f"    提取: {asset}")
                        extracted.add(asset)
                        break
    except Exception as e:
        pass  # 图标提取失败不影响主流程

def extract_addon_xml_from_zip(zip_path):
    """从 zip 中提取 addon.xml 内容，返回 (addon_id, version, xml_content)"""
    try:
        with zipfile.ZipFile(zip_path, 'r') as zf:
            for name in zf.namelist():
                if name.endswith('addon.xml') and name.count('/') == 1:
                    content = zf.read(name).decode('utf-8')
                    # 去掉 XML 声明行，避免匹配到 <?xml version="1.0"?>
                    body = content
                    if body.startswith("<?xml"):
                        body = body[body.index("?>") + 2:]
                    # 匹配 <addon ... version="x.y.z"  (支持跨行)
                    id_match = re.search(r'<addon\b[^>]*?\sid\s*=\s*"([^"]+)"', body, re.DOTALL)
                    ver_match = re.search(r'<addon\b[^>]*?\sversion\s*=\s*"([^"]+)"', body, re.DOTALL)
                    if id_match and ver_match:
                        return id_match.group(1), ver_match.group(1), content
    except Exception as e:
        print(f"  [错误] 读取 {zip_path}: {e}")
    return None, None, None


def generate():
    print("=" * 60)
    print("森芒 Kodi 插件仓库 - addons.xml 生成器")
    print("=" * 60)

    addon_entries = {}  # addon_id -> (version, xml_content, zip_filename)

    # 1. 扫描服务器上所有插件目录
    if not os.path.isdir(SERVER_DIR):
        print(f"[错误] 服务器目录不存在: {SERVER_DIR}")
        return

    dirs = sorted([
        d for d in os.listdir(SERVER_DIR)
        if os.path.isdir(os.path.join(SERVER_DIR, d))
        and not d.startswith('.')
        and d != '.Trash'
        and d != '.well-known'
        and d != 'keymap'
    ])

    print(f"\n扫描到 {len(dirs)} 个目录:\n")

    for dirname in dirs:
        dirpath = os.path.join(SERVER_DIR, dirname)

        # 跳过无法访问的目录
        try:
            zips = sorted([
                f for f in os.listdir(dirpath) if f.endswith('.zip')
            ])
        except (TimeoutError, OSError) as e:
            print(f"  {dirname}/  → [跳过] 目录无法访问: {e}")
            continue

        if not zips:
            print(f"  {dirname}/  (无 zip，跳过)")
            continue

        # 取最新版本的 zip
        latest_zip = None
        latest_ver = (0,)

        for zf in zips:
            # 尝试从文件名解析版本
            ver_match = re.search(r'-(\d[\d.]*)\.zip', zf)
            if ver_match:
                vk = version_key(ver_match.group(1))
                if vk > latest_ver:
                    latest_ver = vk
                    latest_zip = zf
            else:
                latest_zip = zf  # fallback

        if not latest_zip:
            latest_zip = zips[-1]

        zip_path = os.path.join(dirpath, latest_zip)
        addon_id, version, xml_content = extract_addon_xml_from_zip(zip_path)

        if addon_id and xml_content:
            # 去掉 XML 声明行
            lines = xml_content.splitlines()
            if lines and lines[0].startswith("<?xml"):
                xml_content = "\n".join(lines[1:])
            addon_entries[addon_id] = (version, xml_content.strip(), latest_zip)
            print(f"  {dirname}/  →  {addon_id} v{version}  ({latest_zip})")
            # 提取图标和fanart到服务器目录
            extract_assets_from_zip(zip_path, dirpath)
        else:
            print(f"  {dirname}/  →  [跳过] 无法读取 addon.xml")

    # 2. 加入仓库本身
    print(f"\n添加仓库自身:")
    if os.path.exists(REPO_ADDON_XML):
        with open(REPO_ADDON_XML, 'r', encoding='utf-8') as f:
            repo_xml = f.read()
        body = repo_xml
        if body.startswith("<?xml"):
            body = body[body.index("?>") + 2:]
        id_match = re.search(r'<addon\b[^>]*?\sid\s*=\s*"([^"]+)"', body, re.DOTALL)
        ver_match = re.search(r'<addon\b[^>]*?\sversion\s*=\s*"([^"]+)"', body, re.DOTALL)
        if id_match and ver_match:
            lines = repo_xml.splitlines()
            if lines and lines[0].startswith("<?xml"):
                repo_xml = "\n".join(lines[1:])
            addon_entries[id_match.group(1)] = (
                ver_match.group(1),
                repo_xml.strip(),
                f"repository.senmang-{ver_match.group(1)}.zip"
            )
            print(f"  repository.senmang/  →  {id_match.group(1)} v{ver_match.group(1)}")

    # 3. 合并写入 addons.xml
    print(f"\n生成 addons.xml...")
    xml_output = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<addons>\n'
    for addon_id, (version, xml_content, zip_name) in sorted(addon_entries.items()):
        xml_output += xml_content + "\n"
    xml_output += "</addons>\n"

    addons_xml_path = os.path.join(SERVER_DIR, "addons.xml")
    with open(addons_xml_path, 'w', encoding='utf-8') as f:
        f.write(xml_output)

    # 4. 生成 MD5
    hash_md5 = hashlib.md5()
    with open(addons_xml_path, 'rb') as f:
        for chunk in iter(lambda: f.read(4096), b""):
            hash_md5.update(chunk)
    md5 = hash_md5.hexdigest()

    md5_path = os.path.join(SERVER_DIR, "addons.xml.md5")
    with open(md5_path, 'w', encoding='utf-8') as f:
        f.write(md5)

    print(f"  addons.xml  写入完成 (共 {len(addon_entries)} 个插件)")
    print(f"  addons.xml.md5  = {md5}")

    # 5. 创建仓库 zip
    print(f"\n生成仓库安装包...")
    repo_id = "repository.senmang"
    repo_version = "1.0.0"

    if os.path.exists(REPO_ADDON_XML):
        body = open(REPO_ADDON_XML).read()
        if body.startswith("<?xml"):
            body = body[body.index("?>") + 2:]
        id_match = re.search(r'<addon\b[^>]*?\sid\s*=\s*"([^"]+)"', body, re.DOTALL)
        ver_match = re.search(r'<addon\b[^>]*?\sversion\s*=\s*"([^"]+)"', body, re.DOTALL)
        if id_match:
            repo_id = id_match.group(1)
        if ver_match:
            repo_version = ver_match.group(1)

    repo_zip_name = f"{repo_id}-{repo_version}.zip"

    # 写到服务器
    repo_dir = os.path.join(SERVER_DIR, repo_id)
    os.makedirs(repo_dir, exist_ok=True)

    repo_zip_path = os.path.join(repo_dir, repo_zip_name)
    with zipfile.ZipFile(repo_zip_path, 'w', zipfile.ZIP_DEFLATED) as zf:
        zf.write(REPO_ADDON_XML, f"{repo_id}/addon.xml")
        # 从 addon.xml 读取图标文件名，自动打包
        repo_xml_content = open(REPO_ADDON_XML, 'r', encoding='utf-8').read()
        icon_match = re.search(r'<icon>([^<]+)</icon>', repo_xml_content)
        if icon_match:
            icon_file = icon_match.group(1).strip()
            icon_path = os.path.join(os.path.dirname(REPO_ADDON_XML), icon_file)
            if os.path.exists(icon_path):
                zf.write(icon_path, f"{repo_id}/{icon_file}")
        # 如果有 fanart 也打包
        fanart_path_jpg = os.path.join(os.path.dirname(REPO_ADDON_XML), "fanart.jpg")
        fanart_path_png = os.path.join(os.path.dirname(REPO_ADDON_XML), "fanart.png")
        for fp in [fanart_path_jpg, fanart_path_png]:
            if os.path.exists(fp):
                zf.write(fp, f"{repo_id}/{os.path.basename(fp)}")

    print(f"  {repo_zip_path}")

    # 也放在根目录方便直接下载
    root_zip_path = os.path.join(SERVER_DIR, repo_zip_name)
    import shutil
    shutil.copy2(repo_zip_path, root_zip_path)
    print(f"  {root_zip_path}")

    # 6. 输出摘要
    print(f"\n{'=' * 60}")
    print(f"完成!")
    print(f"  仓库地址: https://addon.senmang.cn/")
    print(f"  安装包:   https://addon.senmang.cn/{repo_zip_name}")
    print(f"  插件数:   {len(addon_entries)} 个")
    print(f"{'=' * 60}")


if __name__ == "__main__":
    generate()
