#!/usr/bin/env python3
"""
weekly-report.py — task-timers.json에서 지난 N일간의 메트릭을 집계하여
마크다운 리포트를 자동 생성한다.
"""

import argparse
import json
import os
import sys
from collections import defaultdict
from datetime import datetime, timedelta
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple

_WORKSPACE_ROOT = os.environ.get("WORKSPACE_ROOT", str(Path(__file__).resolve().parent.parent))
TASK_TIMERS_PATH = str(Path(_WORKSPACE_ROOT) / "memory" / "task-timers.json")
REPORTS_DIR = str(Path(_WORKSPACE_ROOT) / "memory" / "reports" / "weekly")


# ---------------------------------------------------------------------------
# 데이터 로딩
# ---------------------------------------------------------------------------


def load_tasks(path: str) -> List[Dict[str, Any]]:
    """task-timers.json을 읽어 작업 목록을 반환한다."""
    if not os.path.exists(path):
        print(f"[ERROR] task-timers.json 파일을 찾을 수 없습니다: {path}", file=sys.stderr)
        sys.exit(1)

    try:
        with open(path, "r", encoding="utf-8") as f:
            raw = json.load(f)
    except json.JSONDecodeError as e:
        print(f"[ERROR] task-timers.json 파싱 실패: {e}", file=sys.stderr)
        sys.exit(1)
    except OSError as e:
        print(f"[ERROR] task-timers.json 읽기 실패: {e}", file=sys.stderr)
        sys.exit(1)

    if "tasks" not in raw or not isinstance(raw["tasks"], dict):
        print("[ERROR] task-timers.json 구조가 올바르지 않습니다. 'tasks' 키가 없습니다.", file=sys.stderr)
        sys.exit(1)

    return list(raw["tasks"].values())


# ---------------------------------------------------------------------------
# 날짜 파싱 유틸
# ---------------------------------------------------------------------------


def parse_dt(value: Optional[str]) -> Optional[datetime]:
    """ISO 8601 문자열을 datetime으로 변환한다. 실패 시 None 반환.

    task-timers.json의 타임스탬프는 로컬 시간(KST) + timezone 정보 없음.
    timezone-naive 그대로 반환하여 로컬 시간 기반 비교를 일관되게 유지한다.
    """
    if not value:
        return None
    # 'Z' suffix 제거 (UTC 명시 시에도 로컬 시간 비교를 위해 naive로 처리)
    value = value.replace("Z", "").replace("+00:00", "")
    try:
        dt = datetime.fromisoformat(value)
        # tzinfo가 있어도 제거하여 timezone-naive로 통일
        return dt.replace(tzinfo=None)
    except ValueError:
        return None


def now_local() -> datetime:
    """로컬 시간 기준 현재 datetime (timezone-naive)."""
    return datetime.now()


# ---------------------------------------------------------------------------
# 필터링
# ---------------------------------------------------------------------------


def filter_tasks_by_period(
    tasks: List[Dict[str, Any]],
    since: datetime,
    until: datetime,
) -> List[Dict[str, Any]]:
    """
    start_time 또는 end_time이 [since, until] 범위 내에 있는 작업만 반환한다.
    """
    result: List[Dict[str, Any]] = []
    for task in tasks:
        st = parse_dt(task.get("start_time"))
        et = parse_dt(task.get("end_time"))
        in_range = False
        if st and since <= st <= until:
            in_range = True
        if et and since <= et <= until:
            in_range = True
        if in_range:
            result.append(task)
    return result


# ---------------------------------------------------------------------------
# 메트릭 집계
# ---------------------------------------------------------------------------


def aggregate(tasks: List[Dict[str, Any]]) -> Dict[str, Any]:
    """집계 결과 딕셔너리를 반환한다."""

    total = len(tasks)
    completed = sum(1 for t in tasks if t.get("status") == "completed")
    stale = sum(1 for t in tasks if t.get("stale_at") or t.get("status") == "stale")
    running = sum(1 for t in tasks if t.get("status") == "running")

    # QC 집계
    qc_pass = qc_fail = qc_warn = qc_na = 0
    for t in tasks:
        qc = t.get("qc_result")
        if qc is None or qc == "" or "qc_result" not in t:
            qc_na += 1
        elif str(qc).upper() == "PASS":
            qc_pass += 1
        elif str(qc).upper() == "FAIL":
            qc_fail += 1
        elif str(qc).upper() in ("WARN", "WARNING"):
            qc_warn += 1
        else:
            qc_na += 1

    qc_total_rated = qc_pass + qc_fail + qc_warn
    qc_fail_pct = round(qc_fail / qc_total_rated * 100, 1) if qc_total_rated > 0 else None

    # 팀별 집계
    # team_id가 None이거나 빈 문자열인 경우 "(unknown)"으로 처리
    team_stats: Dict[str, Dict[str, Any]] = defaultdict(
        lambda: {
            "total": 0,
            "completed": 0,
            "stale": 0,
            "running": 0,
            "duration_seconds_list": [],
            "qc_pass": 0,
            "qc_fail": 0,
            "qc_warn": 0,
            "qc_na": 0,
        }
    )

    stale_details: List[Dict[str, Any]] = []

    for t in tasks:
        team = t.get("team_id") or "(unknown)"
        s = team_stats[team]
        s["total"] += 1

        status = t.get("status", "")
        if status == "completed":
            s["completed"] += 1
        elif status == "running":
            s["running"] += 1

        is_stale = bool(t.get("stale_at")) or status == "stale"
        if is_stale:
            s["stale"] += 1
            stale_details.append(t)

        dur = t.get("duration_seconds")
        if dur is not None:
            try:
                s["duration_seconds_list"].append(float(dur))
            except (TypeError, ValueError):
                pass

        qc = t.get("qc_result")
        if "qc_result" not in t or qc is None or qc == "":
            s["qc_na"] += 1
        elif str(qc).upper() == "PASS":
            s["qc_pass"] += 1
        elif str(qc).upper() == "FAIL":
            s["qc_fail"] += 1
        elif str(qc).upper() in ("WARN", "WARNING"):
            s["qc_warn"] += 1
        else:
            s["qc_na"] += 1

    # 팀별 평균 소요시간 (분 단위)
    team_avg_minutes: Dict[str, Optional[float]] = {}
    for team, s in team_stats.items():
        lst = s["duration_seconds_list"]
        if lst:
            avg_sec = sum(lst) / len(lst)
            team_avg_minutes[team] = round(avg_sec / 60, 1)
        else:
            team_avg_minutes[team] = None

    return {
        "total": total,
        "completed": completed,
        "stale": stale,
        "running": running,
        "qc_pass": qc_pass,
        "qc_fail": qc_fail,
        "qc_warn": qc_warn,
        "qc_na": qc_na,
        "qc_fail_pct": qc_fail_pct,
        "team_stats": dict(team_stats),
        "team_avg_minutes": team_avg_minutes,
        "stale_details": stale_details,
    }


# ---------------------------------------------------------------------------
# 장기 running 작업 감지 (전체 데이터에서)
# ---------------------------------------------------------------------------


def find_long_running(
    all_tasks: List[Dict[str, Any]],
    threshold_hours: float = 1.0,
) -> List[Tuple[Dict[str, Any], float]]:
    """현재 running 상태이며 threshold_hours 이상 경과한 작업을 반환한다."""
    now = now_local()
    result: List[Tuple[Dict[str, Any], float]] = []
    for t in all_tasks:
        if t.get("status") != "running":
            continue
        st = parse_dt(t.get("start_time"))
        if st is None:
            continue
        elapsed_hours = (now - st).total_seconds() / 3600
        if elapsed_hours >= threshold_hours:
            result.append((t, elapsed_hours))
    result.sort(key=lambda x: x[1], reverse=True)
    return result


# ---------------------------------------------------------------------------
# 리포트 생성
# ---------------------------------------------------------------------------


def format_stale_reason(task: Dict[str, Any]) -> str:
    reason = task.get("stale_reason") or task.get("status") or "stale"
    return str(reason)


def build_report(
    metrics: Dict[str, Any],
    long_running: List[Tuple[Dict[str, Any], float]],
    report_date: datetime,
    since: datetime,
    until: datetime,
) -> str:
    date_str = report_date.strftime("%Y-%m-%d")
    since_str = since.strftime("%Y-%m-%d")
    until_str = until.strftime("%Y-%m-%d")

    lines: List[str] = []

    # 헤더
    lines.append(f"# 주간 메트릭 리포트 — {date_str}")
    lines.append(f"> 집계 기간: {since_str} ~ {until_str}")
    lines.append("")

    # 요약
    lines.append("## 요약")
    total = metrics["total"]
    completed = metrics["completed"]
    stale = metrics["stale"]
    running = metrics["running"]
    lines.append(f"- 총 작업: {total}건 (완료: {completed}건, stale: {stale}건, 진행중: {running}건)")

    qc_fail_pct = metrics["qc_fail_pct"]
    qc_pass = metrics["qc_pass"]
    qc_fail = metrics["qc_fail"]
    qc_warn = metrics["qc_warn"]
    qc_na = metrics["qc_na"]
    if qc_fail_pct is None:
        pct_str = "N/A"
    else:
        pct_str = f"{qc_fail_pct}%"
    lines.append(f"- QC FAIL 비율: {pct_str} (PASS: {qc_pass}, FAIL: {qc_fail}, WARN: {qc_warn}, N/A: {qc_na})")
    lines.append("")

    # 팀별 통계
    lines.append("## 팀별 통계")
    lines.append("| 팀 | 작업수 | 완료 | stale | 평균소요시간 | QC PASS | QC FAIL |")
    lines.append("|---|---|---|---|---|---|---|")

    team_stats = metrics["team_stats"]
    team_avg_minutes = metrics["team_avg_minutes"]

    # 작업수 내림차순 정렬
    sorted_teams = sorted(team_stats.keys(), key=lambda t: team_stats[t]["total"], reverse=True)

    for team in sorted_teams:
        s = team_stats[team]
        avg = team_avg_minutes.get(team)
        avg_str = f"{avg}분" if avg is not None else "N/A"
        lines.append(
            f"| {team} | {s['total']} | {s['completed']} | {s['stale']} "
            f"| {avg_str} | {s['qc_pass']} | {s['qc_fail']} |"
        )
    lines.append("")

    # stale 작업 상세
    lines.append("## stale 작업 상세 (최근 7일)")
    stale_details: List[Dict[str, Any]] = metrics["stale_details"]
    if stale_details:
        for t in stale_details:
            task_id = t.get("task_id", "N/A")
            team = t.get("team_id") or "(unknown)"
            st = parse_dt(t.get("start_time"))
            date_part = st.strftime("%Y-%m-%d") if st else "N/A"
            reason = format_stale_reason(t)
            lines.append(f"- {task_id} ({team}, {date_part}, {reason})")
    else:
        lines.append("- (해당 없음)")
    lines.append("")

    # 장기 running 작업 경고
    lines.append("## 장기 running 작업 경고 (현재 running 상태)")
    if long_running:
        for task, elapsed_h in long_running:
            task_id = task.get("task_id", "N/A")
            team = task.get("team_id") or "(unknown)"
            st = parse_dt(task.get("start_time"))
            start_str = st.strftime("%Y-%m-%d %H:%M") if st else "N/A"
            elapsed_str = f"{round(elapsed_h, 1)}시간"
            lines.append(f"- {task_id}: {team}, 시작: {start_str} ({elapsed_str} 경과)")
    else:
        lines.append("- (해당 없음)")
    lines.append("")

    return "\n".join(lines)


# ---------------------------------------------------------------------------
# 출력 경로 결정 및 저장
# ---------------------------------------------------------------------------


def resolve_output_path(override: Optional[str], report_date: datetime) -> str:
    if override:
        return override
    date_str = report_date.strftime("%Y-%m-%d")
    os.makedirs(REPORTS_DIR, exist_ok=True)
    return os.path.join(REPORTS_DIR, f"weekly-{date_str}.md")


def save_report(path: str, content: str) -> None:
    dirpath = os.path.dirname(path)
    if dirpath:
        os.makedirs(dirpath, exist_ok=True)
    with open(path, "w", encoding="utf-8") as f:
        f.write(content)


# ---------------------------------------------------------------------------
# CLI 진입점
# ---------------------------------------------------------------------------


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(description="task-timers.json에서 주간 메트릭 리포트를 생성합니다.")
    parser.add_argument(
        "--days",
        type=int,
        default=7,
        metavar="N",
        help="집계 기간 (기본: 7일)",
    )
    parser.add_argument(
        "--output",
        type=str,
        default=None,
        metavar="PATH",
        help="출력 파일 경로 오버라이드",
    )
    parser.add_argument(
        "--print",
        action="store_true",
        dest="print_output",
        help="콘솔에도 출력",
    )
    return parser.parse_args()


def main() -> None:
    args = parse_args()

    # 날짜 범위 계산 (로컬 시간 기준)
    now = now_local()

    if args.days != 7:
        # --days 옵션이 명시적으로 지정된 경우: 기존 동작 유지
        until = now
        since = now - timedelta(days=args.days)
    else:
        # 기본(7일): 전주 월~일 기간 계산
        today = now.date()
        this_monday = today - timedelta(days=today.weekday())  # 0=월요일
        prev_monday = this_monday - timedelta(days=7)
        prev_sunday = this_monday - timedelta(days=1)
        since = datetime.combine(prev_monday, datetime.min.time())
        until = datetime.combine(prev_sunday, datetime.max.time())

    # 데이터 로딩
    all_tasks = load_tasks(TASK_TIMERS_PATH)

    # 기간 내 작업 필터링
    period_tasks = filter_tasks_by_period(all_tasks, since, until)

    # 메트릭 집계
    metrics = aggregate(period_tasks)

    # 장기 running 작업 감지 (전체 데이터 대상)
    long_running = find_long_running(all_tasks)

    # 리포트 생성
    report_date = now_local()
    report_content = build_report(metrics, long_running, report_date, since, until)

    # 저장
    output_path = resolve_output_path(args.output, report_date)
    save_report(output_path, report_content)

    # 콘솔 출력
    if args.print_output:
        print(report_content)

    print(f"[OK] 리포트 저장 완료: {output_path}", file=sys.stderr)
    sys.exit(0)


if __name__ == "__main__":
    main()
