#!/usr/bin/env python3
"""validate_factor_lifecycle_completeness_v1.py — FACTOR_LIFECYCLE_COMPLETENESS_V1

실측 기반 팩터 생애주기 레지스트리 정합성을 검증한다.
- spec/factor_lifecycle_registry.yaml과 Temp/factor_shadow_eligibility_v1.json를 대조.
- 실측상 BLOCKED인데 명세상 shadow/active로 지정된 하드 정합성 위반 탐지 (FAIL)
- 실측상 ELIGIBLE인데 명세상 draft로 묶여서 shadow 승격 자격이 충분한 팩터들 탐지 및 요약 보고
- spec/13_formula_registry.yaml와 factor_lifecycle_registry.yaml 간의 팩터 개수 정합성 검증 (누락 시 WARN)

출력: Temp/factor_lifecycle_completeness_v1.json
"""
from __future__ import annotations

import argparse
import json
import yaml
from pathlib import Path
from typing import Any

ROOT = Path(__file__).resolve().parents[1]
TEMP = ROOT / "Temp"
FORMULA_ID = "FACTOR_LIFECYCLE_COMPLETENESS_V1"


def main() -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument("--registry", default="spec/factor_lifecycle_registry.yaml")
    ap.add_argument("--eligibility", default="Temp/factor_shadow_eligibility_v1.json")
    ap.add_argument("--formula-reg", default="spec/13_formula_registry.yaml")
    ap.add_argument("--out", default="Temp/factor_lifecycle_completeness_v1.json")
    args = ap.parse_args()

    reg_path = ROOT / args.registry if not Path(args.registry).is_absolute() else Path(args.registry)
    elig_path = ROOT / args.eligibility if not Path(args.eligibility).is_absolute() else Path(args.eligibility)
    freg_path = ROOT / args.formula_reg if not Path(args.formula_reg).is_absolute() else Path(args.formula_reg)
    out_path = ROOT / args.out if not Path(args.out).is_absolute() else Path(args.out)

    if not reg_path.exists():
        print(f"[ERROR] Registry not found: {reg_path}")
        return 1
    if not elig_path.exists():
        print(f"[ERROR] Eligibility file not found: {elig_path}")
        return 1

    # Load inputs
    registry_data = yaml.safe_load(reg_path.read_text(encoding="utf-8")) or {}
    eligibility_data = json.loads(elig_path.read_text(encoding="utf-8")) or {}
    
    formula_reg_data = {}
    if freg_path.exists():
        formula_reg_data = yaml.safe_load(freg_path.read_text(encoding="utf-8")) or {}

    factors = registry_data.get("factors") or []
    elig_rows = eligibility_data.get("rows") or []
    elig_map = {r["factor_id"]: r for r in elig_rows if "factor_id" in r}

    # 1. 팩터 개수 정합성 검증
    # formula_registry의 formulas 목록
    freg_formulas = formula_reg_data.get("formula_registry", {}).get("formulas", {})
    freg_keys = set(freg_formulas.keys())
    registry_keys = {f.get("factor_id") for f in factors if isinstance(f, dict)}
    
    missing_in_lifecycle = list(freg_keys - registry_keys)
    extra_in_lifecycle = list(registry_keys - freg_keys)

    violations = []
    shadow_ready_candidates = []
    
    # 2. 실측-명세 정합성 검사
    for f in factors:
        if not isinstance(f, dict):
            continue
        fid = f.get("factor_id", "UNKNOWN")
        promotion_gate = f.get("promotion_gate", "draft").lower()
        
        elig_info = elig_map.get(fid)
        if not elig_info:
            violations.append({
                "factor_id": fid,
                "type": "MISSING_ELIGIBILITY_DATA",
                "message": f"실측 섀도우 적격성 데이터에 팩터 {fid}가 누락되었습니다."
            })
            continue
            
        eligibility = elig_info.get("eligibility", "UNKNOWN")
        
        # 하드 위반: 데이터 결측(BLOCKED 또는 NO_REQUIRED_DATA)인데 shadow나 active로 지정된 경우
        if eligibility in ("BLOCKED", "NO_REQUIRED_DATA") and promotion_gate in ("shadow", "active", "candidate"):
            violations.append({
                "factor_id": fid,
                "type": "PROMOTION_VIOLATION",
                "message": f"팩터 {fid}는 실측 데이터 결측 상태({eligibility})이나 명세 상 {promotion_gate}로 승급되어 있습니다."
            })
            
        # shadow 승격 자격이 충분한 draft 팩터 탐지
        if eligibility == "ELIGIBLE" and promotion_gate == "draft":
            shadow_ready_candidates.append({
                "factor_id": fid,
                "coverage_pct": elig_info.get("coverage_pct"),
                "present_count": elig_info.get("present_count"),
                "required_field_count": elig_info.get("required_field_count")
            })

    # 전체 게이트 상태 결정
    # 하드 위반(PROMOTION_VIOLATION 등)이 있으면 FAIL
    gate_status = "FAIL" if violations else "PASS"
    
    # 3. 결과 JSON 조립
    result = {
        "formula_id": FORMULA_ID,
        "gate": gate_status,
        "summary": {
            "total_factors_in_lifecycle": len(factors),
            "total_formulas_in_registry": len(freg_keys),
            "missing_factors_count": len(missing_in_lifecycle),
            "extra_factors_count": len(extra_in_lifecycle),
            "shadow_ready_count": len(shadow_ready_candidates),
            "violation_count": len(violations),
        },
        "shadow_ready_candidates": shadow_ready_candidates,
        "missing_in_lifecycle": missing_in_lifecycle,
        "extra_in_lifecycle": extra_in_lifecycle,
        "violations": violations,
        "note": (
            "FACTOR_LIFECYCLE_COMPLETENESS_V1: 팩터 생애주기 명세와 실측 데이터 일치성 검증. "
            "BLOCKED/NO_REQUIRED_DATA 팩터가 shadow/active로 승급되어 있으면 FAIL 판정. "
            "draft 상태 중 GatherTradingData.json에 모든 입력 필드가 실측된 팩터는 shadow_ready_candidates로 분류."
        )
    }

    out_path.parent.mkdir(parents=True, exist_ok=True)
    out_path.write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
    
    print(f"[{FORMULA_ID}] gate={gate_status} total={len(factors)} shadow_ready={len(shadow_ready_candidates)} violations={len(violations)}")
    if shadow_ready_candidates:
        print(f"  Shadow-ready candidates (first 5): {[c['factor_id'] for c in shadow_ready_candidates[:5]]}")
    if violations:
        print(f"  [VIOLATION] First violation: {violations[0]['message']}")
        
    return 0 if gate_status == "PASS" else 1


if __name__ == "__main__":
    raise SystemExit(main())
