#!/usr/bin/env python3
"""
TVBox 本地构建脚本
将解密的 JSON 配置文件转换为本地资源版本

功能：
1. 下载 spider 并命名为 spider.jar
2. 下载 ext 中的 JSON 文件到 json 目录
3. 下载 JS 文件到 js 目录
4. 下载 api 中的 PY 文件到 py 目录
5. 生成本地版本的 JSON 配置文件
"""

import json
import os
import re
import sys
import hashlib
import requests
from pathlib import Path
from urllib.parse import urlparse


class TVBox本地构建器:
    def __init__(self, 输入文件, 输出目录="output"):
        self.输入文件 = 输入文件
        self.输出目录 = Path(输出目录)
        self.数据 = None
        self.session = requests.Session()
        self.session.headers.update({
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
        })

    def 加载数据(self):
        """加载 JSON 数据"""
        with open(self.输入文件, 'r', encoding='utf-8') as f:
            self.数据 = json.load(f)
        print(f"✓ 加载配置文件: {self.输入文件}")
        return True

    def 创建目录结构(self):
        """创建输出目录结构"""
        dirs = ['json', 'js', 'py']
        for d in dirs:
            (self.输出目录 / d).mkdir(parents=True, exist_ok=True)
        print(f"✓ 创建目录结构: {', '.join(dirs)}")

    def 计算MD5(self, 文件路径):
        """计算文件的 MD5"""
        md5 = hashlib.md5()
        with open(文件路径, 'rb') as f:
            while chunk := f.read(8192):
                md5.update(chunk)
        return md5.hexdigest()

    def 替换代理域名(self, url):
        """将代理域名替换为直接访问 raw.githubusercontent.com"""
        # 匹配各种 GitHub 代理域名
        proxy_patterns = [
            (r'https?://git\.yylx\.win/raw\.githubusercontent\.com/', 'https://raw.githubusercontent.com/'),
            (r'https?://gh-proxy\.com/https://raw\.githubusercontent\.com/', 'https://raw.githubusercontent.com/'),
            (r'https?://[^/]+/https://raw\.githubusercontent\.com/', 'https://raw.githubusercontent.com/'),
        ]

        for pattern, replacement in proxy_patterns:
            if re.match(pattern, url):
                new_url = re.sub(pattern, replacement, url)
                if new_url != url:
                    print(f"  替换代理: {url[:60]}...")
                    print(f"       ->: {new_url[:60]}...")
                    return new_url

        return url

    def 下载文件(self, url, 保存路径):
        """下载文件，支持大小写文件名重试"""
        # 替换代理域名为直接访问
        url = self.替换代理域名(url)

        # 解析 URL 获取文件名
        parsed = urlparse(url)
        path = parsed.path
        文件名 = Path(path).name
        目录 = Path(path).parent
        保存目录 = Path(保存路径).parent

        # 尝试的文件名列表：原始 -> 大写 -> 小写
        尝试列表 = [文件名]

        # 如果文件名有扩展名，尝试大小写变体
        if '.' in 文件名:
            名称部分, 扩展名 = 文件名.rsplit('.', 1)
            大写文件名 = f"{名称部分.upper()}.{扩展名}"
            小写文件名 = f"{名称部分.lower()}.{扩展名}"

            if 大写文件名 != 文件名:
                尝试列表.append(大写文件名)
            if 小写文件名 != 文件名 and 小写文件名 != 大写文件名:
                尝试列表.append(小写文件名)

        for 尝试文件名 in 尝试列表:
            # 构建尝试的 URL
            尝试路径 = str(目录 / 尝试文件名)
            尝试url = parsed._replace(path=尝试路径).geturl()
            # 用实际文件名构建保存路径
            实际保存路径 = 保存目录 / 尝试文件名

            try:
                print(f"  下载: {尝试url}")
                resp = self.session.get(尝试url, timeout=30, allow_redirects=True)
                resp.raise_for_status()
                with open(实际保存路径, 'wb') as f:
                    f.write(resp.content)
                print(f"  ✓ 保存到: {实际保存路径}")
                # 返回实际下载的文件名（用于更新配置）
                return True, 尝试文件名
            except Exception as e:
                print(f"  ✗ 下载失败: {e}")
                continue

        return False, None

    def 下载spider(self):
        """下载 spider 文件"""
        spider_url = self.数据.get('spider', '')
        if not spider_url:
            print("✗ 未找到 spider 配置")
            return False

        # 提取 URL（去掉 ;md5;xxx 部分）
        url = spider_url.split(';')[0]
        spider_path = self.输出目录 / 'spider.jar'

        print(f"\n=== 下载 Spider ===")
        url = self.替换代理域名(url)

        try:
            print(f"  下载: {url}")
            resp = self.session.get(url, timeout=30, allow_redirects=True)
            resp.raise_for_status()
            with open(spider_path, 'wb') as f:
                f.write(resp.content)
            print(f"  ✓ 保存到: {spider_path}")
            md5 = self.计算MD5(spider_path)
            print(f"  MD5: {md5}")
            self.数据['spider'] = f"./spider.jar;md5;{md5}"
            return True
        except Exception as e:
            print(f"  ✗ 下载失败: {e}")
            return False

    def 是URL(self, 路径):
        """判断是否为URL"""
        try:
            result = urlparse(路径)
            return all([result.scheme, result.netloc])
        except:
            return False

    def 提取文件名(self, url):
        """从 URL 提取文件名"""
        path = urlparse(url).path
        name = Path(path).name
        # 如果没有扩展名，根据内容添加
        if '.' not in name:
            name += '.txt'
        return name

    def 下载ext文件(self):
        """下载 ext 中的 JSON 文件"""
        print(f"\n=== 下载 ext 文件 ===")
        下载计数 = 0

        for site in self.数据.get('sites', []):
            ext = site.get('ext', '')

            # 处理字符串类型的 ext（可能是 URL）
            if isinstance(ext, str) and self.是URL(ext):
                文件名 = self.提取文件名(ext)
                # 根据扩展名决定保存目录
                if 文件名.endswith('.json'):
                    保存路径 = self.输出目录 / 'json' / 文件名
                    成功, 实际文件名 = self.下载文件(ext, 保存路径)
                    if 成功:
                        site['ext'] = f"./json/{实际文件名}"
                        下载计数 += 1
                elif 文件名.endswith('.js'):
                    保存路径 = self.输出目录 / 'js' / 文件名
                    成功, 实际文件名 = self.下载文件(ext, 保存路径)
                    if 成功:
                        site['ext'] = f"./js/{实际文件名}"
                        下载计数 += 1

            # 处理字典类型的 ext
            elif isinstance(ext, dict):
                for key, value in ext.items():
                    if isinstance(value, str) and self.是URL(value):
                        文件名 = self.提取文件名(value)
                        if 文件名.endswith('.json'):
                            保存路径 = self.输出目录 / 'json' / 文件名
                            成功, 实际文件名 = self.下载文件(value, 保存路径)
                            if 成功:
                                ext[key] = f"./json/{实际文件名}"
                                下载计数 += 1

        print(f"  共下载 {下载计数} 个 ext 文件")
        return 下载计数

    def 下载api文件(self):
        """下载 api 中的 PY 文件（JS 文件保持原链接）"""
        print(f"\n=== 下载 api 文件 ===")
        下载计数 = 0

        for site in self.数据.get('sites', []):
            api = site.get('api', '')

            # 处理以 http 开头的 api（仅下载 PY 文件，JS 保持原链接）
            if isinstance(api, str) and api.startswith('http'):
                文件名 = self.提取文件名(api)
                if 文件名.endswith('.py'):
                    保存路径 = self.输出目录 / 'py' / 文件名
                    成功, 实际文件名 = self.下载文件(api, 保存路径)
                    if 成功:
                        site['api'] = f"./py/{实际文件名}"
                        下载计数 += 1
                elif 文件名.endswith('.js'):
                    # JS 文件不下载，保持原链接
                    print(f"  跳过 JS (保持原链接): {api}")

        print(f"  共下载 {下载计数} 个 api 文件")
        return 下载计数

    def 下载lives文件(self):
        """下载 lives 中的文件"""
        print(f"\n=== 下载 lives 文件 ===")
        下载计数 = 0

        for live in self.数据.get('lives', []):
            url = live.get('url', '')
            if isinstance(url, str) and self.是URL(url):
                文件名 = self.提取文件名(url)
                保存路径 = self.输出目录 / 'json' / 文件名
                成功, 实际文件名 = self.下载文件(url, 保存路径)
                if 成功:
                    live['url'] = f"./json/{实际文件名}"
                    下载计数 += 1

        print(f"  共下载 {下载计数} 个 lives 文件")
        return 下载计数

    def 压缩flags(self, flags):
        """压缩 flags 列表，去除带空格的重复项"""
        if not isinstance(flags, list):
            return flags

        # 去除带空格的重复项（如 "优 酷" -> "优酷"）
        compressed = []
        seen = set()

        for flag in flags:
            # 去除空格
            no_space = flag.replace(' ', '')
            if no_space not in seen:
                seen.add(no_space)
                compressed.append(flag)

        print(f"\n=== 压缩 flags ===")
        print(f"  原始: {len(flags)} 项")
        print(f"  压缩后: {len(compressed)} 项")

        return compressed

    def 保存配置(self):
        """保存本地版本的配置文件"""
        输出文件 = self.输出目录 / 'api.json'

        # 压缩 flags（去除带空格的重复项）
        if 'flags' in self.数据:
            self.数据['flags'] = self.压缩flags(self.数据['flags'])

        # 使用 CompactJSONEncoder 格式化输出
        class CompactJSONEncoder(json.JSONEncoder):
            def iterencode(self, o, _one_shot=False):
                def _compact_list(lst, indent_level):
                    pad = '  ' * indent_level
                    if all(isinstance(i, dict) for i in lst):
                        return '[\n' + ',\n'.join([pad + '  ' + json.dumps(i, ensure_ascii=False, separators=(',', ': ')) for i in lst]) + '\n' + pad + ']'
                    return json.dumps(lst, ensure_ascii=False, indent=2)
                def _encode(obj, indent_level=0):
                    pad = '  ' * indent_level
                    if isinstance(obj, dict):
                        lines = [f'"{k}": {_encode(v, indent_level+1)}' for k, v in obj.items()]
                        return '{\n' + pad + '  ' + (',\n' + pad + '  ').join(lines) + '\n' + pad + '}'
                    elif isinstance(obj, list):
                        return _compact_list(obj, indent_level)
                    return json.dumps(obj, ensure_ascii=False)
                return iter([_encode(o)])

        with open(输出文件, 'w', encoding='utf-8') as f:
            json.dump(self.数据, f, ensure_ascii=False, indent=2, cls=CompactJSONEncoder)

        print(f"\n✓ 保存配置文件: {输出文件}")
        return True

    def 构建(self):
        """执行构建流程"""
        print("=" * 50)
        print("TVBox 本地构建工具")
        print("=" * 50)

        # 加载数据
        if not self.加载数据():
            return False

        # 创建目录结构
        self.创建目录结构()

        # 下载各类资源
        self.下载spider()
        self.下载ext文件()
        self.下载api文件()
        self.下载lives文件()

        # 保存配置
        self.保存配置()

        print("\n" + "=" * 50)
        print("✓ 构建完成！")
        print(f"输出目录: {self.输出目录}")
        print("=" * 50)

        return True


def main():
    if len(sys.argv) < 2:
        print("用法:")
        print("  python build_local.py 输入文件 [输出目录]")
        print("")
        print("示例:")
        print("  python build_local.py api.json")
        print("  python build_local.py api.json output")
        print("  python build_local.py https://example.com/api.json output")
        print("")
        print("说明:")
        print("  将解密的 JSON 配置文件转换为本地资源版本")
        print("  自动下载 spider、ext、api、lives 等资源到本地")
        sys.exit(1)

    输入文件 = sys.argv[1]
    输出目录 = sys.argv[2] if len(sys.argv) > 2 else "output"

    构建器 = TVBox本地构建器(输入文件, 输出目录)
    构建器.构建()


if __name__ == "__main__":
    main()
