"""
SI 프로젝트 보고서 자동 생성 엔진

지원 형식:
  - Excel (.xlsx):  WBS 현황 + 이슈 목록 + 산출물 현황 + KPI
  - HTML:           웹 보고서 (대시보드 내 표시)
  - PDF:            HTML → PDF 변환 (weasyprint)
  - DOCX:           Word 보고서 (python-docx)
  - PPTX:           PowerPoint 보고서 (python-pptx)

보고서 유형:
  - daily:   일일 진행 현황 (당일 WBS 완료 + 이슈 발생)
  - weekly:  주간 보고서 (완료율 + 이슈 + 위험 요약)
  - monthly: 월간 보고서 (KPI + 예산 + 이달의 산출물)
"""
from __future__ import annotations

import io
import logging
import os
from datetime import date, datetime, timedelta
from typing import Any, Dict, Optional

logger = logging.getLogger(__name__)


# ── 프로젝트 데이터 수집 ──────────────────────────────────────────────────────

async def collect_project_data(project_id: int, db) -> Dict[str, Any]:
    """보고서에 필요한 전체 프로젝트 데이터 수집."""
    from models import (
        SiProject, WbsItem, WbsStatus,
        ProjectIssue, IssueStatus,
        ProjectMilestone,
        ProjectRisk, RiskLevel,
        Deliverable, DeliverableStatus,
    )
    from sqlalchemy import select, func

    proj = await db.get(SiProject, project_id)
    if not proj:
        raise ValueError(f"프로젝트 {project_id}를 찾을 수 없습니다.")

    today = date.today()

    # WBS 항목
    wbs_items = (await db.execute(
        select(WbsItem).where(WbsItem.project_id == project_id)
        .order_by(WbsItem.wbs_code)
    )).scalars().all()

    leaf_items = [w for w in wbs_items if w.is_leaf]
    completed  = [w for w in leaf_items if w.completion_pct >= 100]
    delayed    = [w for w in leaf_items if w.planned_end and w.planned_end < today and w.completion_pct < 100]

    # 이슈
    issues = (await db.execute(
        select(ProjectIssue).where(ProjectIssue.project_id == project_id)
        .order_by(ProjectIssue.raised_date.desc())
    )).scalars().all()
    open_issues   = [i for i in issues if i.status in (IssueStatus.OPEN, IssueStatus.IN_PROGRESS)]
    closed_issues = [i for i in issues if i.status == IssueStatus.CLOSED]

    # 마일스톤
    milestones = (await db.execute(
        select(ProjectMilestone).where(ProjectMilestone.project_id == project_id)
        .order_by(ProjectMilestone.target_date)
    )).scalars().all()
    upcoming_milestones = [
        m for m in milestones if m.target_date and m.target_date >= today
    ][:3]

    # 위험
    risks = (await db.execute(
        select(ProjectRisk).where(ProjectRisk.project_id == project_id)
    )).scalars().all()
    high_risks = [r for r in risks if r.risk_level in (RiskLevel.HIGH, RiskLevel.CRITICAL)]

    # 산출물
    deliverables = (await db.execute(
        select(Deliverable).where(Deliverable.project_id == project_id)
    )).scalars().all()
    overdue_deliverables = [
        d for d in deliverables
        if d.status == "PENDING" and d.due_date and d.due_date < today
    ]

    # 진척률 계산
    overall_progress = (
        sum(w.completion_pct for w in leaf_items) / len(leaf_items)
        if leaf_items else 0
    )

    # 예산 소진율
    budget_pct = (
        round(proj.budget_used / proj.budget_total * 100, 1)
        if proj.budget_total else 0.0
    )

    return {
        "project":              proj,
        "today":                today,
        "wbs_items":            wbs_items,
        "leaf_items":           leaf_items,
        "completed_wbs":        completed,
        "delayed_wbs":          delayed,
        "issues":               issues,
        "open_issues":          open_issues,
        "closed_issues":        closed_issues,
        "milestones":           milestones,
        "upcoming_milestones":  upcoming_milestones,
        "risks":                risks,
        "high_risks":           high_risks,
        "deliverables":         deliverables,
        "overdue_deliverables": overdue_deliverables,
        "overall_progress":     round(overall_progress, 1),
        "budget_pct":           budget_pct,
        "wbs_total":            len(leaf_items),
        "wbs_done":             len(completed),
        "wbs_delayed":          len(delayed),
        "issue_open":           len(open_issues),
        "issue_closed":         len(closed_issues),
    }


# ── Excel 보고서 ──────────────────────────────────────────────────────────────

def generate_excel(data: Dict[str, Any], report_type: str = "weekly") -> bytes:
    """Excel (.xlsx) 보고서 생성."""
    try:
        import openpyxl
        from openpyxl.styles import (
            Font, PatternFill, Alignment, Border, Side, numbers
        )
        from openpyxl.utils import get_column_letter
    except ImportError:
        raise ValueError("openpyxl 미설치: pip install openpyxl")

    proj = data["project"]
    today = data["today"]

    wb = openpyxl.Workbook()
    wb.remove(wb.active)

    # ── 공통 스타일 ────────────────────────────────────────────
    HEADER_FILL   = PatternFill("solid", fgColor="1E3A5F")
    HEADER_FONT   = Font(bold=True, color="FFFFFF", size=11)
    TITLE_FONT    = Font(bold=True, size=13, color="1E3A5F")
    OK_FILL       = PatternFill("solid", fgColor="D5F5E3")
    WARN_FILL     = PatternFill("solid", fgColor="FEF9E7")
    DANGER_FILL   = PatternFill("solid", fgColor="FADBD8")
    CENTER        = Alignment(horizontal="center", vertical="center", wrap_text=True)
    LEFT          = Alignment(horizontal="left",   vertical="center", wrap_text=True)
    thin = Side(style="thin", color="CCCCCC")
    BORDER = Border(left=thin, right=thin, top=thin, bottom=thin)

    def header_row(ws, headers, row=1):
        for col, h in enumerate(headers, 1):
            c = ws.cell(row=row, column=col, value=h)
            c.font = HEADER_FONT; c.fill = HEADER_FILL
            c.alignment = CENTER; c.border = BORDER

    def data_row(ws, values, row, fills=None):
        for col, val in enumerate(values, 1):
            c = ws.cell(row=row, column=col, value=val)
            c.alignment = LEFT; c.border = BORDER
            if fills and col-1 < len(fills) and fills[col-1]:
                c.fill = fills[col-1]

    # ── 시트 1: 프로젝트 개요 ─────────────────────────────────
    ws1 = wb.create_sheet("프로젝트 개요")
    ws1.column_dimensions["A"].width = 22
    ws1.column_dimensions["B"].width = 35

    ws1["A1"] = f"📋 {proj.project_name}"
    ws1["A1"].font = TITLE_FONT
    ws1.merge_cells("A1:D1")

    kpi_rows = [
        ("프로젝트 코드",   proj.project_code),
        ("현재 단계",       proj.phase),
        ("전체 진척률",     f"{data['overall_progress']}%"),
        ("건강 상태",       proj.health_status),
        ("계획 기간",       f"{proj.planned_start} ~ {proj.planned_end}"),
        ("PM",              proj.pm_user or "—"),
        ("예산 소진율",     f"{data['budget_pct']}%"),
        ("WBS 완료 수",     f"{data['wbs_done']} / {data['wbs_total']}"),
        ("미결 이슈",       f"{data['issue_open']}건"),
        ("지연 WBS",        f"{data['wbs_delayed']}건"),
        ("보고일",          str(today)),
    ]
    for r, (k, v) in enumerate(kpi_rows, 3):
        ws1.cell(row=r, column=1, value=k).font = Font(bold=True)
        ws1.cell(row=r, column=2, value=v)

    # ── 시트 2: WBS 현황 ──────────────────────────────────────
    ws2 = wb.create_sheet("WBS 현황")
    for col, w in zip("ABCDEFG", [10, 40, 20, 15, 15, 12, 25]):
        ws2.column_dimensions[col].width = w

    header_row(ws2, ["WBS 코드", "제목", "단계", "예정 시작", "예정 완료", "진척률(%)", "상태"])
    for r, item in enumerate(data["wbs_items"], 2):
        status = "완료" if item.completion_pct >= 100 else (
            "지연" if item.planned_end and item.planned_end < today and item.completion_pct < 100
            else "진행중"
        )
        fill = OK_FILL if status == "완료" else (DANGER_FILL if status == "지연" else None)
        fills = [None, fill, None, None, None, fill, fill]
        data_row(ws2, [
            item.wbs_code, item.title, item.phase or "",
            str(item.planned_start or ""), str(item.planned_end or ""),
            item.completion_pct, status,
        ], r, fills)

    # ── 시트 3: 이슈 목록 ─────────────────────────────────────
    ws3 = wb.create_sheet("이슈 관리")
    for col, w in zip("ABCDEFG", [18, 15, 35, 20, 12, 18, 30]):
        ws3.column_dimensions[col].width = w

    header_row(ws3, ["이슈 ID", "유형", "제목", "담당자", "심각도", "상태", "발생일"])
    for r, iss in enumerate(data["issues"], 2):
        severity_fill = DANGER_FILL if getattr(iss, "severity", "") in ("CRITICAL", "HIGH") else None
        data_row(ws3, [
            iss.issue_id, iss.issue_type, iss.title,
            iss.assigned_to or "—", getattr(iss, "severity", "—"),
            iss.status, str(iss.raised_date or ""),
        ], r, [None, None, None, None, severity_fill, None, None])

    # ── 시트 4: 산출물 현황 ───────────────────────────────────
    ws4 = wb.create_sheet("산출물 현황")
    for col, w in zip("ABCDEF", [30, 15, 15, 15, 15, 20]):
        ws4.column_dimensions[col].width = w

    header_row(ws4, ["산출물명", "유형", "버전", "상태", "제출기한", "제출일"])
    for r, dlv in enumerate(data["deliverables"], 2):
        status_fill = (
            OK_FILL     if dlv.status == "APPROVED"  else
            DANGER_FILL if dlv.status == "REJECTED"  else
            WARN_FILL   if dlv.status == "REVIEWING" else
            DANGER_FILL if dlv.due_date and dlv.due_date < today and dlv.status == "PENDING"
            else None
        )
        data_row(ws4, [
            dlv.name, dlv.deliverable_type, dlv.version,
            dlv.status,
            str(dlv.due_date or ""),
            str(dlv.submitted_at.date() if dlv.submitted_at else "—"),
        ], r, [None, None, None, status_fill, None, None])

    # ── 시트 5: 위험 관리 ─────────────────────────────────────
    ws5 = wb.create_sheet("위험 관리")
    for col, w in zip("ABCDE", [18, 35, 12, 12, 40]):
        ws5.column_dimensions[col].width = w

    header_row(ws5, ["위험 ID", "제목", "레벨", "상태", "대응 계획"])
    for r, risk in enumerate(data["risks"], 2):
        fill = DANGER_FILL if getattr(risk, "risk_level", "") in ("HIGH", "CRITICAL") else WARN_FILL
        data_row(ws5, [
            risk.risk_id, risk.title,
            getattr(risk, "risk_level", "—"), risk.status,
            risk.mitigation or "",
        ], r, [None, None, fill, None, None])

    buf = io.BytesIO()
    wb.save(buf)
    return buf.getvalue()


# ── HTML 보고서 ──────────────────────────────────────────────────────────────

def generate_html(data: Dict[str, Any], report_type: str = "weekly") -> str:
    """HTML 보고서 생성 (대시보드 표시 + PDF 변환용)."""
    try:
        from jinja2 import Template
    except ImportError:
        raise ValueError("jinja2 미설치: pip install jinja2")

    proj  = data["project"]
    today = data["today"]

    template_str = """<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<style>
  body { font-family: 'Noto Sans KR', Arial, sans-serif; margin: 30px; color: #333; font-size: 13px; }
  h1 { color: #1e3a5f; border-bottom: 3px solid #1e3a5f; padding-bottom: 8px; }
  h2 { color: #2c5282; margin-top: 24px; font-size: 15px; }
  .kpi-grid { display: flex; gap: 16px; flex-wrap: wrap; margin: 16px 0; }
  .kpi-card { background: #ebf8ff; border-radius: 8px; padding: 12px 18px; min-width: 130px; text-align: center; }
  .kpi-card .value { font-size: 24px; font-weight: bold; color: #2b6cb0; }
  .kpi-card .label { font-size: 11px; color: #718096; margin-top: 4px; }
  table { border-collapse: collapse; width: 100%; margin-bottom: 16px; }
  th { background: #2d3748; color: white; padding: 8px; text-align: left; font-size: 12px; }
  td { padding: 7px 8px; border-bottom: 1px solid #e2e8f0; }
  tr:nth-child(even) { background: #f7fafc; }
  .badge { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 11px; font-weight: bold; }
  .ok    { background: #c6f6d5; color: #276749; }
  .warn  { background: #fefcbf; color: #744210; }
  .danger{ background: #fed7d7; color: #c53030; }
  .meta  { color: #718096; font-size: 12px; }
  @media print { .no-print { display: none; } }
</style>
</head>
<body>
<h1>{{ report_title }}</h1>
<p class="meta">프로젝트: {{ proj.project_name }} ({{ proj.project_code }}) | 보고일: {{ today }} | 단계: {{ proj.phase }}</p>

<h2>📊 KPI 현황</h2>
<div class="kpi-grid">
  <div class="kpi-card"><div class="value">{{ data.overall_progress }}%</div><div class="label">전체 진척률</div></div>
  <div class="kpi-card"><div class="value">{{ data.wbs_done }}/{{ data.wbs_total }}</div><div class="label">WBS 완료</div></div>
  <div class="kpi-card"><div class="value {{ 'danger' if data.wbs_delayed > 0 else 'ok' }}">{{ data.wbs_delayed }}</div><div class="label">지연 항목</div></div>
  <div class="kpi-card"><div class="value {{ 'danger' if data.issue_open > 0 else 'ok' }}">{{ data.issue_open }}</div><div class="label">미결 이슈</div></div>
  <div class="kpi-card"><div class="value">{{ data.budget_pct }}%</div><div class="label">예산 소진율</div></div>
  <div class="kpi-card"><div class="value {{ 'danger' if proj.health_status == 'RED' else 'warn' if proj.health_status == 'YELLOW' else 'ok' }}">{{ proj.health_status }}</div><div class="label">건강 상태</div></div>
</div>

<h2>📋 WBS 지연 현황 ({{ data.wbs_delayed }}건)</h2>
{% if data.delayed_wbs %}
<table>
<tr><th>WBS 코드</th><th>제목</th><th>예정 완료</th><th>진척률</th></tr>
{% for item in data.delayed_wbs %}
<tr>
  <td>{{ item.wbs_code }}</td>
  <td>{{ item.title }}</td>
  <td><span class="badge danger">{{ item.planned_end }}</span></td>
  <td>{{ item.completion_pct }}%</td>
</tr>
{% endfor %}
</table>
{% else %}<p style="color:#48bb78">✅ 지연 WBS 없음</p>{% endif %}

<h2>🔴 미결 이슈 ({{ data.issue_open }}건)</h2>
{% if data.open_issues %}
<table>
<tr><th>이슈 ID</th><th>제목</th><th>유형</th><th>담당자</th><th>발생일</th></tr>
{% for iss in data.open_issues[:10] %}
<tr>
  <td>{{ iss.issue_id }}</td>
  <td>{{ iss.title }}</td>
  <td>{{ iss.issue_type }}</td>
  <td>{{ iss.assigned_to or '—' }}</td>
  <td>{{ iss.raised_date }}</td>
</tr>
{% endfor %}
</table>
{% else %}<p style="color:#48bb78">✅ 미결 이슈 없음</p>{% endif %}

<h2>📦 산출물 제출 현황</h2>
<table>
<tr><th>산출물명</th><th>유형</th><th>버전</th><th>상태</th><th>제출기한</th></tr>
{% for dlv in data.deliverables %}
<tr>
  <td>{{ dlv.name }}</td>
  <td>{{ dlv.deliverable_type }}</td>
  <td>{{ dlv.version }}</td>
  <td><span class="badge {{ 'ok' if dlv.status == 'APPROVED' else 'danger' if dlv.status == 'REJECTED' else 'warn' }}">{{ dlv.status }}</span></td>
  <td>{{ dlv.due_date or '—' }}</td>
</tr>
{% endfor %}
</table>

{% if data.upcoming_milestones %}
<h2>🏁 다가오는 마일스톤</h2>
<table>
<tr><th>마일스톤</th><th>목표일</th><th>상태</th></tr>
{% for m in data.upcoming_milestones %}
<tr><td>{{ m.name }}</td><td>{{ m.target_date }}</td><td>{{ m.status }}</td></tr>
{% endfor %}
</table>
{% endif %}

{% if data.high_risks %}
<h2>⚠️ 고위험 요소 ({{ data.high_risks|length }}건)</h2>
<table>
<tr><th>위험 ID</th><th>제목</th><th>레벨</th><th>대응 계획</th></tr>
{% for r in data.high_risks %}
<tr>
  <td>{{ r.risk_id }}</td>
  <td>{{ r.title }}</td>
  <td><span class="badge danger">{{ r.risk_level }}</span></td>
  <td>{{ r.mitigation or '—' }}</td>
</tr>
{% endfor %}
</table>
{% endif %}

<p class="meta" style="margin-top:32px;border-top:1px solid #e2e8f0;padding-top:12px">
  생성: GUARDiA ITSM | {{ today }}
</p>
</body>
</html>"""

    title_map = {
        "daily":   f"일일 보고서 ({today})",
        "weekly":  f"주간 보고서 ({today} 기준)",
        "monthly": f"월간 보고서 ({today.strftime('%Y년 %m월')})",
    }

    t = Template(template_str)
    return t.render(
        proj=proj,
        data=data,
        today=today,
        report_title=title_map.get(report_type, "보고서"),
    )


# ── PDF 보고서 ───────────────────────────────────────────────────────────────

def generate_pdf(html_content: str) -> bytes:
    """HTML → PDF 변환."""
    try:
        import weasyprint
        pdf_bytes = weasyprint.HTML(string=html_content).write_pdf()
        return pdf_bytes
    except ImportError:
        logger.warning("weasyprint 미설치 — HTML 반환")
        return html_content.encode("utf-8")
    except Exception as e:
        logger.error("PDF 변환 오류: %s", e)
        return html_content.encode("utf-8")


# ── DOCX 보고서 ──────────────────────────────────────────────────────────────

def generate_docx(data: Dict[str, Any], report_type: str = "weekly") -> bytes:
    """Word (.docx) 보고서 생성."""
    try:
        from docx import Document
        from docx.shared import Pt, RGBColor, Cm
        from docx.enum.text import WD_ALIGN_PARAGRAPH
    except ImportError:
        raise ValueError("python-docx 미설치: pip install python-docx")

    proj  = data["project"]
    today = data["today"]
    doc   = Document()

    # 제목
    rtype_kor = {"daily": "일일", "weekly": "주간", "monthly": "월간"}.get(report_type, "")
    title = doc.add_heading(
        f"{proj.project_name} — {rtype_kor}보고서",
        level=0
    )
    title.alignment = WD_ALIGN_PARAGRAPH.CENTER

    doc.add_paragraph(f"보고일: {today}  |  단계: {proj.phase}  |  PM: {proj.pm_user or '—'}")

    # KPI 테이블
    doc.add_heading("KPI 현황", level=1)
    kpi_table = doc.add_table(rows=1, cols=4)
    kpi_table.style = "Table Grid"
    for cell, text in zip(kpi_table.rows[0].cells, ["항목", "계획", "실적", "상태"]):
        cell.text = text

    kpi_rows = [
        ("전체 진척률", "—", f"{data['overall_progress']}%",
         "양호" if data['overall_progress'] >= 80 else "주의"),
        ("WBS 완료율", f"{data['wbs_total']}건",
         f"{data['wbs_done']}건 ({round(data['wbs_done']/data['wbs_total']*100 if data['wbs_total'] else 0, 1)}%)",
         "양호" if data['wbs_delayed'] == 0 else "지연"),
        ("예산 소진율", "100%", f"{data['budget_pct']}%",
         "양호" if data['budget_pct'] <= 80 else "주의"),
        ("미결 이슈", "0건", f"{data['issue_open']}건",
         "양호" if data['issue_open'] == 0 else "주의"),
    ]
    for row_data in kpi_rows:
        row = kpi_table.add_row()
        for cell, val in zip(row.cells, row_data):
            cell.text = val

    # 지연 WBS
    if data["delayed_wbs"]:
        doc.add_heading(f"WBS 지연 현황 ({data['wbs_delayed']}건)", level=1)
        t = doc.add_table(rows=1, cols=4)
        t.style = "Table Grid"
        for c, h in zip(t.rows[0].cells, ["WBS 코드", "제목", "예정 완료", "진척률"]):
            c.text = h
        for item in data["delayed_wbs"]:
            r = t.add_row()
            for c, v in zip(r.cells, [item.wbs_code, item.title, str(item.planned_end), f"{item.completion_pct}%"]):
                c.text = v

    # 이슈
    if data["open_issues"]:
        doc.add_heading(f"미결 이슈 ({data['issue_open']}건)", level=1)
        t = doc.add_table(rows=1, cols=4)
        t.style = "Table Grid"
        for c, h in zip(t.rows[0].cells, ["이슈 ID", "제목", "담당자", "발생일"]):
            c.text = h
        for iss in data["open_issues"][:10]:
            r = t.add_row()
            for c, v in zip(r.cells, [iss.issue_id, iss.title, iss.assigned_to or "—", str(iss.raised_date or "")]):
                c.text = v

    doc.add_paragraph(f"\n생성: GUARDiA ITSM | {today}")

    buf = io.BytesIO()
    doc.save(buf)
    return buf.getvalue()


# ── PPTX 보고서 ──────────────────────────────────────────────────────────────

def generate_pptx(data: Dict[str, Any], report_type: str = "weekly") -> bytes:
    """PowerPoint (.pptx) 보고서 생성."""
    try:
        from pptx import Presentation
        from pptx.util import Inches, Pt, Emu
        from pptx.dml.color import RGBColor
        from pptx.enum.text import PP_ALIGN
    except ImportError:
        raise ValueError("python-pptx 미설치: pip install python-pptx")

    proj  = data["project"]
    today = data["today"]
    prs   = Presentation()
    prs.slide_width  = Inches(13.33)
    prs.slide_height = Inches(7.5)

    DARK_BLUE = RGBColor(0x1E, 0x3A, 0x5F)
    WHITE     = RGBColor(0xFF, 0xFF, 0xFF)

    def add_slide(layout_idx=5):
        layout = prs.slide_layouts[layout_idx]
        return prs.slides.add_slide(layout)

    def set_bg(slide, color=RGBColor(0xF7, 0xFA, 0xFC)):
        from pptx.util import Emu
        fill = slide.background.fill
        fill.solid()
        fill.fore_color.rgb = color

    def add_text_box(slide, text, left, top, width, height, font_size=14, bold=False, color=None):
        txBox = slide.shapes.add_textbox(Inches(left), Inches(top), Inches(width), Inches(height))
        tf = txBox.text_frame
        tf.word_wrap = True
        p = tf.paragraphs[0]
        run = p.add_run()
        run.text = text
        run.font.size = Pt(font_size)
        run.font.bold = bold
        if color:
            run.font.color.rgb = color
        return txBox

    # ── 슬라이드 1: 표지 ──────────────────────────────────────
    slide1 = add_slide(6)  # blank
    set_bg(slide1, DARK_BLUE)
    add_text_box(slide1, proj.project_name, 1, 2, 11, 1.5, font_size=32, bold=True, color=WHITE)
    rtype_name = {"daily": "일일", "weekly": "주간", "monthly": "월간"}.get(report_type, "")
    add_text_box(slide1, f"{rtype_name} 보고서", 1, 3.8, 11, 0.8, font_size=20, color=RGBColor(0xBD, 0xE3, 0xFF))
    add_text_box(slide1, f"보고일: {today}  |  단계: {proj.phase}", 1, 5, 11, 0.6, font_size=14, color=RGBColor(0xA0, 0xC4, 0xFF))

    # ── 슬라이드 2: KPI 요약 ──────────────────────────────────
    slide2 = add_slide(6)
    set_bg(slide2)
    add_text_box(slide2, "KPI 현황", 0.5, 0.3, 12, 0.6, font_size=22, bold=True, color=DARK_BLUE)

    kpis = [
        ("전체 진척률", f"{data['overall_progress']}%"),
        ("WBS 완료",    f"{data['wbs_done']}/{data['wbs_total']}"),
        ("지연 항목",   f"{data['wbs_delayed']}건"),
        ("미결 이슈",   f"{data['issue_open']}건"),
        ("예산 소진",   f"{data['budget_pct']}%"),
        ("건강 상태",   proj.health_status),
    ]
    for i, (label, value) in enumerate(kpis):
        x = 0.5 + (i % 3) * 4.2
        y = 1.5 + (i // 3) * 2.2
        box = slide2.shapes.add_shape(
            1, Inches(x), Inches(y), Inches(3.8), Inches(1.8)
        )
        box.fill.solid(); box.fill.fore_color.rgb = RGBColor(0xEB, 0xF8, 0xFF)
        box.line.color.rgb = RGBColor(0x90, 0xCF, 0xF8)
        tf = box.text_frame
        tf.word_wrap = True
        p1 = tf.paragraphs[0]
        p1.alignment = PP_ALIGN.CENTER
        r1 = p1.add_run(); r1.text = value
        r1.font.size = Pt(28); r1.font.bold = True
        r1.font.color.rgb = DARK_BLUE
        p2 = tf.add_paragraph()
        p2.alignment = PP_ALIGN.CENTER
        r2 = p2.add_run(); r2.text = label
        r2.font.size = Pt(12); r2.font.color.rgb = RGBColor(0x71, 0x80, 0x96)

    # ── 슬라이드 3: WBS 현황 ──────────────────────────────────
    if data["delayed_wbs"]:
        slide3 = add_slide(6)
        set_bg(slide3)
        add_text_box(slide3, f"WBS 지연 현황 ({data['wbs_delayed']}건)", 0.5, 0.3, 12, 0.6, 22, True, DARK_BLUE)

        from pptx.util import Inches as I
        tbl = slide3.shapes.add_table(
            min(len(data["delayed_wbs"]) + 1, 8), 4,
            I(0.5), I(1.2), I(12), I(4.5)
        ).table
        headers = ["WBS 코드", "제목", "예정 완료", "진척률"]
        for col, h in enumerate(headers):
            tbl.cell(0, col).text = h
        for r, item in enumerate(data["delayed_wbs"][:6], 1):
            vals = [item.wbs_code, item.title[:30], str(item.planned_end), f"{item.completion_pct}%"]
            for col, v in enumerate(vals):
                tbl.cell(r, col).text = v

    # ── 슬라이드 4: 이슈 요약 ─────────────────────────────────
    if data["open_issues"]:
        slide4 = add_slide(6)
        set_bg(slide4)
        add_text_box(slide4, f"미결 이슈 ({data['issue_open']}건)", 0.5, 0.3, 12, 0.6, 22, True, DARK_BLUE)

        from pptx.util import Inches as I
        tbl = slide4.shapes.add_table(
            min(len(data["open_issues"]) + 1, 8), 4,
            I(0.5), I(1.2), I(12), I(4.5)
        ).table
        headers = ["이슈 ID", "제목", "유형", "담당자"]
        for col, h in enumerate(headers):
            tbl.cell(0, col).text = h
        for r, iss in enumerate(data["open_issues"][:6], 1):
            vals = [iss.issue_id, iss.title[:30], iss.issue_type, iss.assigned_to or "—"]
            for col, v in enumerate(vals):
                tbl.cell(r, col).text = v

    buf = io.BytesIO()
    prs.save(buf)
    return buf.getvalue()


# ── 보고서 유형 통합 생성 ─────────────────────────────────────────────────────

async def generate_report(
    project_id:  int,
    report_type: str,   # daily | weekly | monthly
    output_fmt:  str,   # excel | html | pdf | docx | pptx
    db,
) -> tuple[bytes, str, str]:
    """
    보고서 생성 통합 함수.

    Returns:
        (content_bytes, media_type, filename)
    """
    data = await collect_project_data(project_id, db)
    proj = data["project"]
    today = data["today"]

    rtype = {"daily": "일일", "weekly": "주간", "monthly": "월간"}.get(report_type, report_type)
    base_name = f"{proj.project_code}_{rtype}보고서_{today}"

    if output_fmt == "excel":
        content  = generate_excel(data, report_type)
        mime     = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
        filename = f"{base_name}.xlsx"

    elif output_fmt == "html":
        content  = generate_html(data, report_type).encode("utf-8")
        mime     = "text/html; charset=utf-8"
        filename = f"{base_name}.html"

    elif output_fmt == "pdf":
        html_str = generate_html(data, report_type)
        content  = generate_pdf(html_str)
        mime     = "application/pdf"
        filename = f"{base_name}.pdf"

    elif output_fmt == "docx":
        content  = generate_docx(data, report_type)
        mime     = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
        filename = f"{base_name}.docx"

    elif output_fmt == "pptx":
        content  = generate_pptx(data, report_type)
        mime     = "application/vnd.openxmlformats-officedocument.presentationml.presentation"
        filename = f"{base_name}.pptx"

    else:
        raise ValueError(f"지원하지 않는 형식: {output_fmt}")

    return content, mime, filename
