#!/usr/bin/env python3
"""daily_digest.py — 매일 09:00 어제 자동 머지(Tier 1) 다이제스트 + 랜덤 10% 회장 검토.

데이터 소스: memory/audit/auto-merge.log (JSONL append-only)
출력: Telegram 단일 메시지 카드
환경변수:
  ANU_CONFIRM_BOT_TOKEN, ANU_CONFIRM_CHAT_ID, WORKSPACE_ROOT, GH_REPO
  DIGEST_SAMPLE_RATE (기본 0.1), DIGEST_DRY_RUN (=1 발송 안 함)
"""
from __future__ import annotations

import json
import logging
import math
import os
import random
import sys
import urllib.parse
import urllib.request
from datetime import datetime, timedelta, timezone
from pathlib import Path

logger = logging.getLogger("daily_digest")
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s")

KST = timezone(timedelta(hours=9))
WORKSPACE = Path(os.environ.get("WORKSPACE_ROOT", "/home/jay/workspace"))
AUDIT_LOG = WORKSPACE / "memory" / "audit" / "auto-merge.log"
BOT_TOKEN = os.environ.get("ANU_CONFIRM_BOT_TOKEN", "")
CHAT_ID = os.environ.get("ANU_CONFIRM_CHAT_ID", "")
SAMPLE_RATE = float(os.environ.get("DIGEST_SAMPLE_RATE", "0.1"))
DRY_RUN = os.environ.get("DIGEST_DRY_RUN", "0") == "1"
GH_REPO = os.environ.get("GH_REPO", "JonghyukJeon/workspace")


def _yesterday_range_kst(now: datetime | None = None) -> tuple[datetime, datetime]:
    """어제 00:00 ~ 오늘 00:00 KST."""
    if now is None:
        now = datetime.now(KST)
    today_start = now.replace(hour=0, minute=0, second=0, microsecond=0)
    yesterday_start = today_start - timedelta(days=1)
    return yesterday_start, today_start


def _parse_audit_log(path: Path) -> list[dict]:
    """JSONL audit log 파싱. 미존재/손상 시 빈 리스트 반환 (graceful fallback)."""
    if not path.exists():
        logger.info(f"audit log 미존재: {path} — 빈 결과 반환")
        return []
    records: list[dict] = []
    try:
        with path.open("r", encoding="utf-8") as f:
            for raw in f:
                line = raw.strip()
                if not line:
                    continue
                try:
                    records.append(json.loads(line))
                except json.JSONDecodeError as exc:
                    logger.warning(f"audit log 라인 파싱 실패 (skip): {exc}")
    except OSError as exc:
        logger.error(f"audit log 읽기 실패: {exc}")
    return records


def _filter_yesterday_tier1_merged(records: list[dict], yesterday_start: datetime, today_start: datetime) -> list[dict]:
    """어제 KST 사이 + tier=tier1 + outcome=merged 만 필터."""
    filtered = []
    for r in records:
        if r.get("tier") != "tier1":
            continue
        if r.get("outcome") != "merged":
            continue
        ts_str = r.get("timestamp", "")
        try:
            ts = datetime.fromisoformat(ts_str)
            if ts.tzinfo is None:
                ts = ts.replace(tzinfo=KST)
        except ValueError:
            continue
        if yesterday_start <= ts < today_start:
            filtered.append(r)
    return filtered


def _filter_tier3_pending(records: list[dict], yesterday_start: datetime, today_start: datetime) -> list[dict]:
    """어제 escalated tier3 (인간 게이트 대기)."""
    filtered = []
    for r in records:
        if r.get("tier") != "tier3":
            continue
        if r.get("outcome") != "escalated":
            continue
        ts_str = r.get("timestamp", "")
        try:
            ts = datetime.fromisoformat(ts_str)
            if ts.tzinfo is None:
                ts = ts.replace(tzinfo=KST)
        except ValueError:
            continue
        if yesterday_start <= ts < today_start:
            filtered.append(r)
    return filtered


def _sample_for_review(merged: list[dict], rate: float = SAMPLE_RATE, rng: random.Random | None = None) -> list[dict]:
    """랜덤 sample. ceil(len*rate). 1건 미만은 0건. rng 주입 가능 (테스트용)."""
    if not merged:
        return []
    n = math.ceil(len(merged) * rate)
    n = max(0, min(n, len(merged)))
    if n == 0:
        return []
    _rng: random.Random = rng if rng is not None else random.Random()
    return _rng.sample(merged, n)


def _format_card(merged: list[dict], reviews: list[dict], pending: list[dict], date_str: str) -> str:
    """Telegram 카드 텍스트 생성."""
    lines = [f"★ 자동 머지 일일 다이제스트 ({date_str})", ""]

    if merged:
        lines.append(f"Tier 1 자동 머지: {len(merged)}건")
        for r in merged[:20]:
            tid = r.get("task_id", "?")
            diff_loc = r.get("diff_loc", "?")
            files = len(r.get("diff_files", [])) if isinstance(r.get("diff_files"), list) else "?"
            lines.append(f"- {tid}: Lv.{r.get('level', '1')}, {files}파일, {diff_loc}LOC")
        if len(merged) > 20:
            lines.append(f"... (총 {len(merged)}건)")
    else:
        lines.append("Tier 1 자동 머지: 0건")

    lines.append("")
    if reviews:
        lines.append(f"★ 랜덤 검토 대상 ({int(SAMPLE_RATE*100)}%): {len(reviews)}건")
        for r in reviews:
            tid = r.get("task_id", "?")
            pr = r.get("pr_number", "?")
            url = f"https://github.com/{GH_REPO}/pull/{pr}" if pr and pr != "?" else "(PR 없음)"
            lines.append(f"- {tid}: 회장 검토 권장 — {url}")
    else:
        lines.append("★ 랜덤 검토 대상: 0건")

    lines.append("")
    lines.append(f"★ Tier 3 인간 게이트 (대기): {len(pending)}건")
    for r in pending[:10]:
        lines.append(f"- {r.get('task_id', '?')}: 회장 명시 승인 필요")

    return "\n".join(lines)


def _telegram_send(text: str, chat_id: str, bot_token: str) -> dict:
    """Telegram sendMessage. dry-run 또는 token 미설정 시 dict로 반환만."""
    if DRY_RUN or not bot_token or not chat_id:
        logger.info("Telegram dry-run mode")
        return {"ok": False, "dry_run": True, "text": text}
    url = f"https://api.telegram.org/bot{bot_token}/sendMessage"
    payload = urllib.parse.urlencode({"chat_id": chat_id, "text": text}).encode("utf-8")
    try:
        with urllib.request.urlopen(url, data=payload, timeout=10) as resp:
            return json.loads(resp.read().decode("utf-8"))
    except Exception as exc:
        logger.error(f"Telegram 발송 실패: {exc}")
        return {"ok": False, "error": str(exc)}


def run_digest(now: datetime | None = None, audit_path: Path | None = None) -> dict:
    """다이제스트 1회 실행.

    반환: {"merged_count": N, "reviews": [...], "pending": [...], "card_text": "...", "telegram_result": {...}}
    """
    audit_path = audit_path or AUDIT_LOG
    yesterday_start, today_start = _yesterday_range_kst(now)
    records = _parse_audit_log(audit_path)
    merged = _filter_yesterday_tier1_merged(records, yesterday_start, today_start)
    pending = _filter_tier3_pending(records, yesterday_start, today_start)
    reviews = _sample_for_review(merged)

    date_str = yesterday_start.strftime("%Y-%m-%d")
    card = _format_card(merged, reviews, pending, date_str)
    tg_result = _telegram_send(card, CHAT_ID, BOT_TOKEN)

    return {
        "merged_count": len(merged),
        "reviews_count": len(reviews),
        "pending_count": len(pending),
        "card_text": card,
        "telegram_result": tg_result,
        "yesterday": date_str,
    }


def main() -> int:
    result = run_digest()
    print(json.dumps({k: v for k, v in result.items() if k != "card_text"}, ensure_ascii=False, indent=2))
    print("\n=== Card ===\n" + result["card_text"])
    return 0


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