#!/usr/bin/env python3
"""
GUARDiA ESN — 중앙 guardia-rag 색인 훅 (대표 도메인/운영 지식 → esn 테넌트 컬렉션).

알람 원인분석(GraphRAG 의존성 근거)·POS 분류(스키마 grounding)를 위해, ESN 의 대표
도메인 메타데이터(테이블/관계/운영 룰)만 중앙 RAG 의 테넌트별 컬렉션(esn__<TENANT>)에 적재한다.
실제 데이터 행·자격증명·PII(passwordHash·ssh_*)는 색인하지 않는다(보안 불변).

사용:
    python scripts/rag_ingest_esn.py                       # 4개 테넌트 전부
    python scripts/rag_ingest_esn.py LGINNOTEK             # 특정 테넌트만
    RAG_URL=http://127.0.0.1:8020 python scripts/rag_ingest_esn.py

설계 메모:
- 외부 API 0. 중앙 guardia-rag(온프레미스 루프백)만 호출.
- 테넌트 격리: 컬렉션 esn__<TENANT> 로 분리. 한 테넌트 색인이 다른 테넌트에 섞이지 않음.
- 멱등: doc_id 안정 → 재실행 시 upsert(중앙 처리). 실패해도 ESN 동작 영향 없음(보조 스크립트).
"""
import json
import os
import sys
import urllib.request
import urllib.error

RAG_URL = os.environ.get("RAG_URL", "http://127.0.0.1:8020").rstrip("/")
SOLUTION = "esn"
TENANTS = ["LGINNOTEK", "LGIT", "EMART", "ZIOINFO"]

# 대표 도메인/운영 지식 문서 — 알람 의존성 추적·POS 분류 grounding 용.
# (자격증명·PII 제외. 관계·룰·물리 테이블 정의 중심. 테넌트 공통 지식 → 각 테넌트 컬렉션에 적재.)
COMMON_DOCS = [
    {
        "doc_id": "esn_dep_graph",
        "source": "ESN-DEPENDENCY",
        "text": (
            "의존성 관계: esn_hcore_device(게이트웨이/허브) → esn_store(매장) → ESL 단말 → "
            "esn_tag_binding(ESL↔상품 바인딩). 허브 1대 장애 시 하위 매장의 다수 ESL 단말에서 "
            "연쇄(cascading) 알람이 발생한다. 동일 hcore 하위 단말의 동시 알람은 단일 근본원인(허브)으로 "
            "수렴하여 1건으로 그룹핑한다."
        ),
    },
    {
        "doc_id": "esn_alarm_schema",
        "source": "ESN-SCHEMA",
        "text": (
            "테이블 esn_alarm: 장치 알람. 컬럼 - id, tenant_code, store_code, device_id, "
            "alarm_type(통신단절/배터리/표시오류 등), severity(CRITICAL/HIGH/MEDIUM/LOW), message, "
            "occurred_at. 우선순위 산정: 영향 단말 수 × 테넌트 중요도 × SLA 잔여시간 × 중복도 가중합 → P1~P4."
        ),
    },
    {
        "doc_id": "esn_priority_rule",
        "source": "ESN-RULE",
        "text": (
            "우선순위 규칙(폴백): severity CRITICAL→P1, HIGH→P2, MEDIUM→P3, LOW→P4. "
            "단, 영향 단말 수가 많거나 핵심 매장이면 한 단계 상향. 같은 groupKey(동일 근본원인) 파생 "
            "알람은 1건으로 집계한다."
        ),
    },
    {
        "doc_id": "esn_poscvt_schema",
        "source": "ESN-SCHEMA",
        "text": (
            "테이블 esn_pos_cvt: POS→ESL 가격 변환. 컬럼 - id, tenant_code, product_code, price(정가), "
            "sale_price(판매가), status. 분류 룰 — ABNORMAL 판정: 판매가>정가, 음수 가격, 누락, "
            "비현실적 값(예: 0 또는 과대). 그 외 NORMAL. confidence 임계 미만은 수동 검토(needsReview)."
        ),
    },
    {
        "doc_id": "esn_hcore_schema",
        "source": "ESN-SCHEMA",
        "text": (
            "테이블 esn_hcore_device: 게이트웨이/허브. 컬럼 - id, tenant_code, store_code, status"
            "(ONLINE/OFFLINE), child_device_count(하위 단말 수), last_seen_at(마지막 통신). "
            "OFFLINE + 하위 단말 다수 알람 = 허브 장애 시그니처."
        ),
    },
]


def ingest_tenant(tenant: str) -> int:
    collection = f"{SOLUTION}__{tenant}"
    # 문서에 테넌트 메타 부여(컬렉션 격리)
    docs = [{**d, "tenant_code": tenant} for d in COMMON_DOCS]
    payload = {"solution": SOLUTION, "collection": collection, "tenant_code": tenant, "documents": docs}
    data = json.dumps(payload).encode("utf-8")
    req = urllib.request.Request(
        f"{RAG_URL}/rag/ingest",
        data=data,
        headers={"Content-Type": "application/json", "X-Solution-Key": SOLUTION},
        method="POST",
    )
    try:
        with urllib.request.urlopen(req, timeout=120) as resp:
            body = resp.read().decode("utf-8")
            print(f"[OK] ingested {len(docs)} docs into '{collection}'")
            print(f"     response: {body[:200]}")
            return 0
    except urllib.error.HTTPError as e:
        print(f"[WARN] ingest HTTP {e.code} ({collection}) — 중앙 RAG 응답 이상(보조 스크립트). ESN 동작 영향 없음.")
        return 1
    except urllib.error.URLError as e:
        print(f"[WARN] 중앙 guardia-rag 미가용({RAG_URL}) — 색인 보류. 라이브 가동 후 재실행. (요약: {str(e.reason)[:80]})")
        return 1
    except Exception:
        print(f"[WARN] 색인 중 예기치 못한 오류({collection}) — 보류(ESN 동작 영향 없음).")
        return 1


def main() -> int:
    targets = [t.upper() for t in sys.argv[1:]] or TENANTS
    rc = 0
    for t in targets:
        if t not in TENANTS:
            print(f"[SKIP] 알 수 없는 테넌트: {t} (허용: {', '.join(TENANTS)})")
            continue
        rc |= ingest_tenant(t)
    return rc


if __name__ == "__main__":
    sys.exit(main())
