"""大批直通良率专用回刷脚本(gxerp)

只解析检验批的大批归属(BatchSt.zt_batch)并重算各大批的 直通_* 统计,
不重建批次的其他统计数据(那个用 correct_batchst.py)。

用法:
    python scripts/correct_zt_gxerp.py [起始日期] [--fresh] [--workers=N] [--skip-resolve]
    起始日期默认 2026-06-01; --fresh 忽略旧锁重新解析归属并强制重算白料(纠错模式);
    --workers 并发线程数, 默认 8; --skip-resolve 跳过归属解析, 直接用已落库的 zt_batch 算大批
"""
import os
import sys
import django

CUR_DIR = os.path.dirname(os.path.abspath(__file__))
BASE_DIR = os.path.dirname(CUR_DIR)
sys.path.insert(0, BASE_DIR)

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "server.settings")
django.setup()

from django.conf import settings
settings.DEBUG = False  # DEBUG 下 Django 会把每条 SQL 记入内存, 长脚本越跑越慢

import concurrent.futures
import threading
from datetime import datetime
from django.db.models import Q
from apps.wpm.models import BatchSt
from apps.wpm.scripts.batch_gxerp import resolve_zt_big, cal_zt_big
import json
from apps.utils.tools import MyJSONEncoder

_print_lock = threading.Lock()


def main(since: str = "2026-06-01", fresh: bool = False, workers: int = 8, skip_resolve: bool = False):
    since_dt = datetime.strptime(since, "%Y-%m-%d")
    done = [0]

    if skip_resolve:
        # 直接用已落库的归属
        seen = set(BatchSt.objects.filter(
            create_time__gte=since_dt, version=1, zt_batch__isnull=False,
        ).values_list("zt_batch", flat=True).distinct())
        bigs = sorted(seen)
        print(f"跳过归属解析, 已落库大批 {len(bigs)} 个 (workers={workers})", flush=True)
    else:
        cond = Q(data__has_key="尺寸检验_日期") | Q(data__has_key="外观检验_日期")
        if fresh:
            # 纠错模式需覆盖曾被(错误)锁定/计算过的批
            cond = cond | Q(zt_batch__isnull=False) | Q(data__has_key="直通_良率")
        qs = BatchSt.objects.filter(create_time__gte=since_dt, version=1).filter(cond).order_by("create_time")
        items = list(qs)
        total = len(items)
        print(f"检验批 {total} 个, 开始解析大批归属 (fresh={fresh}, workers={workers})", flush=True)

        def resolve_one(bs):
            try:
                # fresh 模式忽略旧锁, 从谱系重新解析(纠正多级拆批错锁)
                big = resolve_zt_big(bs, bs.data or {}, use_lock=not fresh)
                if big and bs.zt_batch != big:
                    bs.zt_batch = big
                    bs.save(update_fields=["zt_batch", "update_time"])
                return big
            except Exception as e:
                with _print_lock:
                    print(f"{bs.batch} 归属解析失败: {e}", flush=True)
                return None
            finally:
                done[0] += 1
                if done[0] % 1000 == 0:
                    with _print_lock:
                        print(f"归属解析 {done[0]}/{total}", flush=True)

        with concurrent.futures.ThreadPoolExecutor(max_workers=workers) as pool:
            results = list(pool.map(resolve_one, items))

        bigs = []  # 保序去重
        seen = set()
        for big in results:
            if big and big not in seen:
                seen.add(big)
                bigs.append(big)

    if fresh and not skip_resolve:
        # 清理: 曾被错当成大批计算过直通字段、如今无任何子批指向的批,
        # 剥离其直通键并解除自指锁定, 交由后续大批计算重新归属
        cleaned = 0
        for bs in BatchSt.objects.filter(
            create_time__gte=since_dt, version=1, data__has_key="直通_良率",
        ).iterator():
            if bs.batch in seen:
                continue
            if BatchSt.objects.filter(zt_batch=bs.batch, version=1).exclude(id=bs.id).exists():
                continue
            data = bs.data
            for k in ["直通_子批次", "直通_总合格数", "直通_白料数", "直通_良率", "直通_口径"]:
                data.pop(k, None)
            bs.data = json.loads(json.dumps(data, cls=MyJSONEncoder))
            if bs.zt_batch == bs.batch:
                bs.zt_batch = None
                bs.save(update_fields=["data", "zt_batch", "update_time"])
            else:
                bs.save(update_fields=["data", "update_time"])
            cleaned += 1
            print(f"清理错误大批标记: {bs.batch}", flush=True)
        print(f"清理 {cleaned} 个", flush=True)

    print(f"共 {len(bigs)} 个大批, 开始计算直通良率", flush=True)
    done[0] = 0

    def cal_one(big):
        try:
            res = cal_zt_big(big, use_white_cache=not fresh) or {}
            done[0] += 1
            with _print_lock:
                print(f"[{done[0]}/{len(bigs)}] {big} 良率={res.get('良率')} 白料={res.get('白料数')} "
                      f"合格={res.get('总合格数')} 子批={res.get('子批数')} 口径={res.get('口径')}", flush=True)
        except Exception as e:
            done[0] += 1
            with _print_lock:
                print(f"[{done[0]}/{len(bigs)}] {big} 计算失败: {e}", flush=True)

    with concurrent.futures.ThreadPoolExecutor(max_workers=workers) as pool:
        list(pool.map(cal_one, bigs))
    print("done")


if __name__ == "__main__":
    fresh = "--fresh" in sys.argv
    skip_resolve = "--skip-resolve" in sys.argv
    workers = 8
    args = []
    for a in sys.argv[1:]:
        if a in ("--fresh", "--skip-resolve"):
            continue
        if a.startswith("--workers="):
            workers = int(a.split("=", 1)[1])
            continue
        args.append(a)
    main(since=args[0] if args else "2026-06-01", fresh=fresh, workers=workers, skip_resolve=skip_resolve)
