#!/usr/bin/env python3
"""原子工具：Job JSON Schema 校验

校验 Job JSON 文件的合法性，composer 模式额外校验布局名称和 source 引用。

用法:
    python validate_job.py job.json
    python validate_job.py job.json --dry-run   # 校验 + 尝试连接数据源

输出 (stdout):
    {"status": "ok", "mode": "...", "summary": {...}}
    {"status": "error", "errors": [...]}

退出码: 0=通过, 1=失败
"""

import sys
import json
import argparse
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent.parent))

from ppt_station.models.job import Job


def main():
    parser = argparse.ArgumentParser(description="Job JSON 校验工具")
    parser.add_argument("job_json", help="Job JSON 文件路径")
    parser.add_argument("--dry-run", action="store_true",
                        help="额外尝试连接数据源（不渲染）")
    args = parser.parse_args()

    job_path = Path(args.job_json)
    if not job_path.exists():
        print(json.dumps({"status": "error", "errors": [f"文件不存在: {job_path}"]},
                         ensure_ascii=False))
        sys.exit(1)

    # 1. 基础 Schema 校验
    errors = []
    try:
        with open(job_path, "r", encoding="utf-8") as f:
            raw = json.load(f)
        job = Job(**raw)
    except json.JSONDecodeError as e:
        print(json.dumps({"status": "error", "errors": [f"JSON 语法错误: {e}"]},
                         ensure_ascii=False))
        sys.exit(1)
    except Exception as e:
        print(json.dumps({"status": "error", "errors": [f"Schema 校验失败: {e}"]},
                         ensure_ascii=False))
        sys.exit(1)

    # 2. Composer 模式额外校验
    if job.mode == "composer":
        from ppt_station.composer.layouts import LAYOUT_REGISTRY

        all_sources = set(job.datasources.keys())
        if job.transforms:
            all_sources.update(job.transforms.keys())

        for i, page in enumerate(job.pages):
            # 校验布局名称
            if page.layout not in LAYOUT_REGISTRY:
                available = ", ".join(LAYOUT_REGISTRY.keys())
                errors.append(f"pages[{i}]: 未知布局 '{page.layout}'。可用: {available}")

            # 校验 datasource/source 引用
            if "datasource" in page.data:
                if page.data["datasource"] not in all_sources:
                    errors.append(
                        f"pages[{i}]: datasource '{page.data['datasource']}' "
                        f"不存在于 datasources/transforms 中"
                    )
            # source 值不在已知数据源中 → 视为脚注文本，不报错

            # 校验嵌套 datasource/source 引用 (two_charts)
            for key in ("left", "right"):
                if key in page.data and isinstance(page.data[key], dict):
                    sub = page.data[key]
                    ds_ref = sub.get("datasource") or sub.get("source")
                    if ds_ref and ds_ref not in all_sources:
                        errors.append(
                            f"pages[{i}].data.{key}: datasource/source '{ds_ref}' "
                            f"不存在于 datasources/transforms 中"
                        )

    if errors:
        print(json.dumps({"status": "error", "errors": errors}, ensure_ascii=False))
        sys.exit(1)

    # 3. 构造摘要
    summary = {"mode": job.mode, "datasources": list(job.datasources.keys())}
    if job.mode == "template":
        summary["slides"] = len(job.slides)
        summary["template"] = job.template.path
    elif job.mode == "composer":
        summary["pages"] = len(job.pages)
        summary["theme"] = job.theme or "jp_finance"
        summary["layouts"] = [p.layout for p in job.pages]

    if job.transforms:
        summary["transforms"] = list(job.transforms.keys())

    # 4. 可选 dry-run：尝试连接数据源
    if args.dry_run and job.datasources:
        from ppt_station.connectors import ConnectorFactory

        ds_status = {}
        for name, ds in job.datasources.items():
            try:
                print(f"尝试连接: {name}", file=sys.stderr)
                df = ConnectorFactory.load_data(name, ds)
                ds_status[name] = {"ok": True, "rows": len(df), "columns": list(df.columns)}
            except Exception as e:
                ds_status[name] = {"ok": False, "error": str(e)}
        summary["datasource_check"] = ds_status

    print(json.dumps({"status": "ok", "summary": summary}, ensure_ascii=False))


if __name__ == "__main__":
    main()
