"""
한화오션 EPU WTM 시스템 설계서 PPT 생성
요구사항정의 → To-Be → 시스템구성도 → 기능정의 → 시스템설계
"""
from pptx import Presentation
from pptx.util import Inches, Pt, Emu
from pptx.dml.color import RGBColor
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
from pptx.enum.shapes import MSO_SHAPE
import os

OUTPUT = os.path.join(os.path.dirname(__file__), "한화오션_EPU_WTM_시스템설계서.pptx")

# 색상
PRIMARY = RGBColor(30, 60, 120)
ACCENT = RGBColor(0, 120, 200)
DARK = RGBColor(40, 40, 40)
BODY = RGBColor(80, 80, 80)
WHITE = RGBColor(255, 255, 255)
LIGHT_BG = RGBColor(240, 245, 255)
GREEN_BG = RGBColor(220, 240, 220)
ORANGE_BG = RGBColor(255, 240, 220)
RED_ACCENT = RGBColor(200, 60, 60)


def add_box(slide, left, top, width, height, text, fill_color=LIGHT_BG, font_color=PRIMARY, font_size=10, bold=True):
    shape = slide.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE, left, top, width, height)
    shape.fill.solid()
    shape.fill.fore_color.rgb = fill_color
    shape.line.color.rgb = PRIMARY
    shape.line.width = Pt(1)
    tf = shape.text_frame
    tf.word_wrap = True
    p = tf.paragraphs[0]
    p.text = text
    p.font.size = Pt(font_size)
    p.font.color.rgb = font_color
    p.font.bold = bold
    p.alignment = PP_ALIGN.CENTER
    tf.paragraphs[0].space_before = Pt(2)
    return shape


def add_textbox(slide, left, top, width, height, text, font_size=10, color=BODY, bold=False, align=PP_ALIGN.LEFT):
    txBox = slide.shapes.add_textbox(left, top, width, height)
    tf = txBox.text_frame
    tf.word_wrap = True
    p = tf.paragraphs[0]
    p.text = text
    p.font.size = Pt(font_size)
    p.font.color.rgb = color
    p.font.bold = bold
    p.alignment = align
    return txBox


def add_multiline_box(slide, left, top, width, height, title, items, fill_color=LIGHT_BG):
    shape = slide.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE, left, top, width, height)
    shape.fill.solid()
    shape.fill.fore_color.rgb = fill_color
    shape.line.color.rgb = PRIMARY
    shape.line.width = Pt(1)
    tf = shape.text_frame
    tf.word_wrap = True
    p = tf.paragraphs[0]
    p.text = title
    p.font.size = Pt(9)
    p.font.color.rgb = PRIMARY
    p.font.bold = True
    for item in items:
        p2 = tf.add_paragraph()
        p2.text = f"• {item}"
        p2.font.size = Pt(7)
        p2.font.color.rgb = BODY
        p2.space_before = Pt(1)
    return shape


def add_arrow(slide, start_left, start_top, end_left, end_top):
    connector = slide.shapes.add_connector(
        1, start_left, start_top, end_left, end_top)  # 1 = straight
    connector.line.color.rgb = PRIMARY
    connector.line.width = Pt(1.5)
    return connector


def add_table(slide, left, top, width, rows_data, col_widths=None):
    rows = len(rows_data)
    cols = len(rows_data[0])
    table_shape = slide.shapes.add_table(rows, cols, left, top, width, Inches(0.3 * rows))
    table = table_shape.table

    if col_widths:
        for i, w in enumerate(col_widths):
            table.columns[i].width = w

    for ri, row in enumerate(rows_data):
        for ci, val in enumerate(row):
            cell = table.cell(ri, ci)
            cell.text = str(val)
            p = cell.text_frame.paragraphs[0]
            p.font.size = Pt(8)
            if ri == 0:
                p.font.bold = True
                p.font.color.rgb = WHITE
                cell.fill.solid()
                cell.fill.fore_color.rgb = PRIMARY
            else:
                p.font.color.rgb = BODY
                if ri % 2 == 0:
                    cell.fill.solid()
                    cell.fill.fore_color.rgb = LIGHT_BG
            p.alignment = PP_ALIGN.CENTER if ci > 0 else PP_ALIGN.LEFT


def title_slide(prs, title, subtitle):
    slide = prs.slides.add_slide(prs.slide_layouts[6])  # blank
    # 배경
    bg = slide.background.fill
    bg.solid()
    bg.fore_color.rgb = PRIMARY
    # 타이틀
    add_textbox(slide, Inches(1), Inches(2), Inches(8), Inches(1),
                title, font_size=32, color=WHITE, bold=True, align=PP_ALIGN.CENTER)
    add_textbox(slide, Inches(1), Inches(3.2), Inches(8), Inches(0.8),
                subtitle, font_size=16, color=RGBColor(180, 200, 230), align=PP_ALIGN.CENTER)
    add_textbox(slide, Inches(1), Inches(5.5), Inches(8), Inches(0.5),
                "아큐라시스템 | 2026년 3월", font_size=12, color=RGBColor(150, 170, 200), align=PP_ALIGN.CENTER)
    return slide


def section_slide(prs, num, title, subtitle=""):
    slide = prs.slides.add_slide(prs.slide_layouts[6])
    bg = slide.background.fill
    bg.solid()
    bg.fore_color.rgb = RGBColor(25, 50, 100)
    # 큰 번호
    add_textbox(slide, Inches(0.8), Inches(1.5), Inches(2), Inches(1.2),
                f"{num:02d}", font_size=72, color=ACCENT, bold=True)
    # 구분선
    shape = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE,
                                    Inches(0.8), Inches(3.0), Inches(8), Pt(3))
    shape.fill.solid()
    shape.fill.fore_color.rgb = ACCENT
    shape.line.fill.background()
    # 타이틀
    add_textbox(slide, Inches(0.8), Inches(3.3), Inches(9), Inches(0.8),
                title, font_size=28, color=WHITE, bold=True)
    # 서브타이틀
    if subtitle:
        add_textbox(slide, Inches(0.8), Inches(4.3), Inches(8), Inches(0.5),
                    subtitle, font_size=12, color=RGBColor(150, 180, 220))
    return slide


def content_slide(prs, title):
    slide = prs.slides.add_slide(prs.slide_layouts[6])
    # 상단 바
    shape = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, 0, 0, Inches(10), Inches(0.6))
    shape.fill.solid()
    shape.fill.fore_color.rgb = PRIMARY
    shape.line.fill.background()
    add_textbox(slide, Inches(0.3), Inches(0.05), Inches(9), Inches(0.5),
                title, font_size=16, color=WHITE, bold=True)
    return slide


def build():
    prs = Presentation()
    prs.slide_width = Inches(10)
    prs.slide_height = Inches(7.5)

    # ============================================================
    # 표지
    # ============================================================
    title_slide(prs,
                "한화오션 EPU\nWork Time Manager (WTM)",
                "시스템 설계서 — 요구사항정의 · To-Be · 시스템구성 · 기능정의 · 설계")

    # ============================================================
    # 목차
    # ============================================================
    slide = content_slide(prs, "목차")
    toc = [
        "01. 프로젝트 개요",
        "02. 요구사항 정의서",
        "03. As-Is / To-Be 분석",
        "04. 시스템 구성도",
        "05. 기능 정의서",
        "06. 데이터베이스 설계",
        "07. API 설계",
        "08. 화면 구성",
        "09. 인터페이스 설계 (SAP/P6/Cognite)",
        "10. 보안 설계",
        "11. 단계별 추진 일정",
    ]
    y = 1.0
    for item in toc:
        add_textbox(slide, Inches(1.5), Inches(y), Inches(7), Inches(0.35),
                    item, font_size=14, color=DARK)
        y += 0.4

    # ============================================================
    # 01. 프로젝트 개요
    # ============================================================
    section_slide(prs, 1, "프로젝트 개요", "WTM 시스템 범위, 기술 스택, 추진 구조")

    slide = content_slide(prs, "01. 프로젝트 개요")
    overview_data = [
        ["항목", "내용"],
        ["프로젝트명", "WTM (Work Time Manager)"],
        ["고객", "한화오션 EPU (Engineering Procurement Unit)"],
        ["목적", "EPC 프로젝트 인력 시수 관리, WBS 연동, 결재, 리포트"],
        ["기능 요구사항", "86개 (PH1 Y=62 / PH2 이관=24)"],
        ["비기능 요구사항", "17개 (NF.1~17)"],
        ["화면 수", "49개 (PH1-1차 26 / PH1-2차 11 / PH2 12)"],
        ["API 수", "79개 (PH1-1: 67 / PH1-2: 6 / PH2: 6)"],
        ["기술 스택", "Spring Boot 3.5.0 + Java 21 (wbx-spring 프레임워크)"],
        ["DB", "Azure SQL (프로필 전환: Oracle/MSSQL/MySQL/PG)"],
        ["인증", "Azure Entra ID SSO + MFA/TOTP"],
    ]
    add_table(slide, Inches(0.5), Inches(1.0), Inches(9),
              overview_data, col_widths=[Inches(2.5), Inches(6.5)])

    # ============================================================
    # 02. 요구사항 정의서
    # ============================================================
    section_slide(prs, 2, "요구사항 정의서", "기능 86개 (Y=62/N=24) + 비기능 17개 + 확인필요사항 7건")

    # 기능 요구사항 요약
    slide = content_slide(prs, "02-1. 기능 요구사항 (86개) — 영역별 분류")
    req_data = [
        ["영역", "PH1 (Y)", "PH2 이관 (N)", "합계"],
        ["User Registration (No.1~7)", "5", "2", "7"],
        ["Login / 인증 (No.8~16)", "8", "1", "9"],
        ["User Home / 권한 (No.17~24)", "6", "2", "8"],
        ["Project / WBS (No.25~41)", "14", "3", "17"],
        ["Resource Assignment (No.42~54)", "6", "7", "13"],
        ["Time Sheet (No.55~69)", "13", "2", "15"],
        ["Approval (No.70~75)", "6", "0", "6"],
        ["Reporting (No.76~86)", "2", "9", "11"],
        ["합계", "62", "24", "86"],
    ]
    add_table(slide, Inches(0.5), Inches(1.0), Inches(9),
              req_data, col_widths=[Inches(4), Inches(2), Inches(2), Inches(1)])

    # 비기능 요구사항
    slide = content_slide(prs, "02-2. 비기능 요구사항 (17개)")
    nf_data = [
        ["No.", "카테고리", "요구사항", "비고"],
        ["NF.1~2", "클라우드", "Azure Hybrid Security Zone 내 구성", "MS Azure"],
        ["NF.3", "보안(서버)", "HIWARE, V3, Secuver TOS", "한화 표준"],
        ["NF.4", "보안(DB)", "Cubeone(암호화), Dbsafer(접근제어)", "한화 표준"],
        ["NF.5", "보안(Cloud)", "Defender + Analytics", "Native"],
        ["NF.6~7", "정보보호", "보안성 심의, 컴플라이언스", "그룹 표준"],
        ["NF.8~10", "모니터링", "onTune(SMS), Maxguage(DB), Jennifer(WAS)", "한화 표준"],
        ["NF.11", "인증(내부)", "Azure Entra ID SSO", ""],
        ["NF.12", "인증(외부)", "2-Way 인증 (MFA)", "TOTP"],
        ["NF.13", "I/F(HR)", "SAP BTP 경유 SuccessFactors 연동", ""],
        ["NF.14", "I/F(P6)", "파일 기반 (물리적 I/F 없음)", ""],
        ["NF.15", "I/F(Cognite)", "Extractor 서버 구성", "PH2"],
        ["NF.16~17", "아키텍처", "이중화, 백업 전략", ""],
    ]
    add_table(slide, Inches(0.3), Inches(1.0), Inches(9.4),
              nf_data, col_widths=[Inches(1), Inches(1.5), Inches(4.5), Inches(1.5)])

    # 확인필요사항
    slide = content_slide(prs, "02-3. 확인필요사항 (7건)")
    confirm_data = [
        ["#", "영역", "내용", "현재 상태"],
        ["1", "User", "파트너사/외주 인력 관리 정책", "파트너 마스터 미연계"],
        ["2", "권한", "사용자 관리규정 및 권한정책", "No.18 확정 (기능 기반 역할)"],
        ["3", "WBS", "WBS별 Resource Plan 방식", "TT에서 투입시수 세팅"],
        ["4", "배정", "인력 배치 시 스케줄 확인 절차", "Discipline별 인력 선정 후 제출"],
        ["5", "EPC", "C단계 시수 입력 방안", "C단계 대상 제외 (EPU만)"],
        ["6", "결재", "업무별 승인 절차", "User→DL→PM 3단계 확정"],
        ["7", "배정", "Project Assignment 프로세스", "SA 생성→PM 배정→확정"],
    ]
    add_table(slide, Inches(0.3), Inches(1.0), Inches(9.4),
              confirm_data, col_widths=[Inches(0.5), Inches(1), Inches(3.5), Inches(3.5)])

    # ============================================================
    # 03. As-Is / To-Be
    # ============================================================
    section_slide(prs, 3, "As-Is / To-Be 분석", "Excel 수작업 → WTM 시스템 전환 효과")

    slide = content_slide(prs, "03. As-Is → To-Be 전환")

    # As-Is 박스들
    add_textbox(slide, Inches(0.5), Inches(0.9), Inches(4), Inches(0.4),
                "As-Is (현재)", font_size=14, color=RED_ACCENT, bold=True)

    as_is = [
        ("Excel 기반 시수 관리", "수작업 입력, 오류 빈번"),
        ("이메일 기반 결재", "추적 불가, 지연"),
        ("P6 WBS 수동 매핑", "버전 관리 어려움"),
        ("리포트 수동 집계", "실시간 분석 불가"),
        ("인력 배정 구두 전달", "이력 관리 없음"),
    ]
    y = 1.4
    for title, desc in as_is:
        add_multiline_box(slide, Inches(0.5), Inches(y), Inches(4), Inches(0.55),
                         title, [desc], fill_color=RGBColor(255, 235, 235))
        y += 0.6

    # 화살표 영역
    add_textbox(slide, Inches(4.5), Inches(2.5), Inches(1), Inches(0.5),
                "→", font_size=36, color=ACCENT, bold=True, align=PP_ALIGN.CENTER)

    # To-Be 박스들
    add_textbox(slide, Inches(5.5), Inches(0.9), Inches(4), Inches(0.4),
                "To-Be (WTM 시스템)", font_size=14, color=ACCENT, bold=True)

    to_be = [
        ("통합 시수 입력 (3종)", "Non-Project/Other/EPC 탭 전환, 규칙 엔진"),
        ("온라인 결재 워크플로우", "User→DL→PM 3단계, 일괄 승인, 실시간 알림"),
        ("P6 WBS 자동 파싱", "Canonical WBS 매핑, 버전 관리, 비교 UI"),
        ("실시간 리포트", "프로젝트별/WBS별 시수 분석, Excel Export"),
        ("체계적 인력 배정", "프로젝트-Discipline-인력 매핑, 이력 관리"),
    ]
    y = 1.4
    for title, desc in to_be:
        add_multiline_box(slide, Inches(5.5), Inches(y), Inches(4), Inches(0.55),
                         title, [desc], fill_color=RGBColor(230, 245, 255))
        y += 0.6

    # 하단 기대효과
    add_textbox(slide, Inches(0.5), Inches(5.0), Inches(9), Inches(0.3),
                "기대효과", font_size=12, color=PRIMARY, bold=True)
    effects = "• 시수 입력 오류 90% 감소   • 결재 처리 시간 70% 단축   • 실시간 프로젝트 시수 현황 파악   • WBS 변경 추적 자동화"
    add_textbox(slide, Inches(0.5), Inches(5.3), Inches(9), Inches(0.5),
                effects, font_size=10, color=BODY)

    # ============================================================
    # 04. 시스템 구성도
    # ============================================================
    section_slide(prs, 4, "시스템 구성도", "전체 아키텍처, 기술 스택, 네트워크 구성")

    # 전체 아키텍처
    slide = content_slide(prs, "04-1. 전체 시스템 아키텍처")

    # 클라이언트 영역
    add_textbox(slide, Inches(0.3), Inches(0.9), Inches(2), Inches(0.3),
                "클라이언트", font_size=10, color=BODY, bold=True)
    clients = ["WBX No-Code", "Web Browser", "Mobile"]
    cx = 0.3
    for c in clients:
        add_box(slide, Inches(cx), Inches(1.2), Inches(1.4), Inches(0.5),
                c, fill_color=RGBColor(200, 220, 255), font_size=8)
        cx += 1.6

    # Nginx
    add_box(slide, Inches(5.5), Inches(1.2), Inches(1.8), Inches(0.5),
            "Nginx (SSL/LB)", fill_color=RGBColor(220, 220, 220), font_color=DARK, font_size=9)

    # Spring Boot WTM
    add_multiline_box(slide, Inches(0.3), Inches(2.2), Inches(4.5), Inches(2.8),
                     "Spring Boot WTM-API (wbx-spring 프레임워크)", [
                         "JWT 인증 / Azure Entra ID SSO / MFA",
                         "RBAC 권한 (dept_scope)",
                         "통합 결재 엔진 (Handler Registry)",
                         "SSE 실시간 알림",
                         "시수 입력 3종 + 규칙 엔진",
                         "WBS/TEAL 관리 + P6 파서",
                         "리포트 (QueryDSL)",
                         "SAP BTP HR 연동",
                     ], fill_color=RGBColor(230, 240, 255))

    # WBX FastAPI
    add_multiline_box(slide, Inches(5.2), Inches(2.2), Inches(2.3), Inches(2.0),
                     "WBX FastAPI (선택)", [
                         "문서관리",
                         "이메일",
                         "게시판/일정",
                         "/api/gw/*"
                     ], fill_color=RGBColor(245, 240, 255))

    # DB
    add_multiline_box(slide, Inches(7.8), Inches(2.2), Inches(2), Inches(1.5),
                     "Database", [
                         "wtm_db (Azure SQL)",
                         "wbx_gw (MySQL)",
                         "프로필 전환 지원"
                     ], fill_color=RGBColor(255, 245, 230))

    # Redis
    add_box(slide, Inches(7.8), Inches(4.0), Inches(1), Inches(0.5),
            "Redis", fill_color=GREEN_BG, font_size=9)

    # Blob Storage
    add_box(slide, Inches(8.9), Inches(4.0), Inches(1), Inches(0.5),
            "Storage", fill_color=ORANGE_BG, font_size=9)

    # SSO
    add_box(slide, Inches(5.2), Inches(4.5), Inches(2.3), Inches(0.5),
            "Azure Entra ID (SSO+MFA)", fill_color=RGBColor(255, 235, 235), font_size=8)

    # SAP BTP
    add_box(slide, Inches(0.3), Inches(5.3), Inches(2), Inches(0.5),
            "SAP BTP (HR)", fill_color=RGBColor(255, 240, 220), font_size=9)

    # P6
    add_box(slide, Inches(2.5), Inches(5.3), Inches(1.5), Inches(0.5),
            "P6 (WBS)", fill_color=RGBColor(255, 240, 220), font_size=9)

    # Cognite
    add_box(slide, Inches(4.2), Inches(5.3), Inches(1.5), Inches(0.5),
            "Cognite", fill_color=RGBColor(255, 240, 220), font_size=9)

    # 기술 스택 슬라이드
    slide = content_slide(prs, "04-2. 기술 스택")
    tech_data = [
        ["레이어", "기술", "버전/비고"],
        ["Language", "Java", "21 (LTS)"],
        ["Framework", "Spring Boot + wbx-spring", "3.5.0"],
        ["Security", "Spring Security 6 + OAuth2", "Entra ID SSO + MFA"],
        ["ORM", "Spring Data JPA + QueryDSL", "Hibernate 6"],
        ["DB", "Azure SQL / Oracle / MySQL / PG", "프로필 전환"],
        ["Migration", "Flyway", "DBMS별 DDL 분리"],
        ["Cache", "Redis", "세션 + 캐시"],
        ["WEB/WAS", "Nginx + Embedded Tomcat", "이중화"],
        ["CI/CD", "GitHub Actions + Azure DevOps", "승인 게이트"],
        ["형상관리", "GitHub + Azure Repos", "SemVer"],
        ["모니터링", "Jennifer + Actuator + Prometheus", "한화 표준"],
        ["Cloud", "Microsoft Azure", "Hybrid Security Zone"],
    ]
    add_table(slide, Inches(0.5), Inches(1.0), Inches(9),
              tech_data, col_widths=[Inches(2), Inches(3.5), Inches(3.5)])

    # ============================================================
    # 05. 기능 정의서
    # ============================================================
    section_slide(prs, 5, "기능 정의서", "시수 입력 3종, WBS/TEAL, 결재 워크플로우, 리포트")

    # 시수 입력
    slide = content_slide(prs, "05-1. 시수 입력 (3종 통합 UI)")

    # 탭 UI 도식
    tabs = ["Non-Project", "Other Project", "EPC Project"]
    tx = 0.5
    for i, tab in enumerate(tabs):
        color = ACCENT if i == 2 else RGBColor(180, 180, 180)
        add_box(slide, Inches(tx), Inches(1.0), Inches(2), Inches(0.4),
                tab, fill_color=RGBColor(230, 240, 255) if i == 2 else RGBColor(245, 245, 245),
                font_color=color, font_size=9)
        tx += 2.2

    # EPC 입력 폼 도식
    fields = [
        "프로젝트: [EPU-2025-001 ▼]",
        "WBS: [E.01.03 Piping Detail ▼]",
        "TEAL: [Detail Engineering ▼]",
    ]
    fy = 1.6
    for field in fields:
        add_textbox(slide, Inches(0.8), Inches(fy), Inches(6), Inches(0.3),
                    field, font_size=10, color=DARK)
        fy += 0.35

    # 주간 그리드
    days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "합계"]
    vals = ["8.0", "8.0", "8.0", "8.0", "4.0", "-", "36.0h"]
    grid_data = [days, vals]
    add_table(slide, Inches(0.5), Inches(2.9), Inches(6.5),
              grid_data, col_widths=[Inches(0.9)] * 7)

    # 규칙 엔진 박스
    add_multiline_box(slide, Inches(7.3), Inches(1.0), Inches(2.5), Inches(2.5),
                     "규칙 엔진", [
                         "일 최소 8시간",
                         "주 최대 52시간",
                         "Activity 최소 1개",
                         "Location별 규칙",
                         "미래 날짜 입력 불가",
                         "초과 시 경고/차단",
                     ], fill_color=ORANGE_BG)

    # 하단 설명
    add_textbox(slide, Inches(0.5), Inches(3.6), Inches(6), Inches(0.3),
                "[임시 저장]  [결재 요청]                         주간 합계: 36.0 / 52h",
                font_size=9, color=BODY)

    ts_desc = [
        ("Non-Project (No.55~59)", "Overhead Type 선택 → TEAL Activity → 시간 입력. WBS/프로젝트 입력 불가"),
        ("Other Project (No.60~62)", "프로젝트 선택 → Canonical WBS L2~4 → Activity → 시간. P6 연동 불필요"),
        ("EPC Project (No.64~69)", "프로젝트 → Canonical WBS L2~5 → TEAL → 시간. Revision 관리(PH1-2)"),
    ]
    fy = 4.2
    for title, desc in ts_desc:
        add_textbox(slide, Inches(0.5), Inches(fy), Inches(9), Inches(0.2),
                    title, font_size=9, color=PRIMARY, bold=True)
        add_textbox(slide, Inches(0.5), Inches(fy + 0.2), Inches(9), Inches(0.25),
                    desc, font_size=8, color=BODY)
        fy += 0.5

    # WBS/TEAL
    slide = content_slide(prs, "05-2. WBS · TEAL 관리")

    # Canonical WBS 구조
    add_textbox(slide, Inches(0.5), Inches(0.9), Inches(4), Inches(0.3),
                "Canonical WBS 구조 (No.34)", font_size=12, color=PRIMARY, bold=True)
    wbs_levels = [
        ["Level", "구분", "설명", "예시"],
        ["L1", "Project", "프로젝트", "EPU-2025-001"],
        ["L2", "Phase", "Engineering/Procurement/Construction/...", "Engineering"],
        ["L3", "Asset/Area", "자산 또는 영역", "Hull Structure"],
        ["L4", "Work/Discipline", "작업 또는 Discipline", "Piping"],
        ["L5", "Deliverable", "산출물 (Eng/SCM only)", "Detail Drawing"],
    ]
    add_table(slide, Inches(0.5), Inches(1.3), Inches(9),
              wbs_levels, col_widths=[Inches(0.8), Inches(1.5), Inches(4), Inches(2.5)])

    # WBS 흐름
    add_textbox(slide, Inches(0.5), Inches(3.5), Inches(4), Inches(0.3),
                "WBS 업로드 흐름", font_size=12, color=PRIMARY, bold=True)
    flow_steps = ["P6 Export\n(PM 업로드)", "WBS 파싱\n(Level 1~5)", "Canonical\nWBS 매핑", "PCM 승인\n버전 등록", "TEAL\nActivity 연결"]
    fx = 0.3
    for i, step in enumerate(flow_steps):
        add_box(slide, Inches(fx), Inches(3.9), Inches(1.6), Inches(0.8),
                step, fill_color=RGBColor(230, 240, 255) if i % 2 == 0 else GREEN_BG,
                font_size=8, font_color=DARK)
        if i < len(flow_steps) - 1:
            add_textbox(slide, Inches(fx + 1.6), Inches(4.05), Inches(0.3), Inches(0.3),
                        "→", font_size=16, color=ACCENT, bold=True, align=PP_ALIGN.CENTER)
        fx += 1.9

    # 결재 워크플로우
    slide = content_slide(prs, "05-3. 결재 워크플로우")

    # 결재 흐름도
    steps = [
        ("User\n시수 입력", RGBColor(200, 220, 255)),
        ("제출\n(결재 요청)", RGBColor(255, 240, 200)),
        ("DL\n1차 결재", RGBColor(220, 240, 220)),
        ("PM\n최종 결재", RGBColor(220, 240, 220)),
        ("승인 완료\n(APPROVED)", RGBColor(200, 255, 200)),
    ]
    sx = 0.3
    for i, (label, color) in enumerate(steps):
        add_box(slide, Inches(sx), Inches(1.3), Inches(1.6), Inches(0.9),
                label, fill_color=color, font_color=DARK, font_size=10)
        if i < len(steps) - 1:
            add_textbox(slide, Inches(sx + 1.6), Inches(1.5), Inches(0.3), Inches(0.3),
                        "→", font_size=18, color=ACCENT, bold=True, align=PP_ALIGN.CENTER)
        sx += 1.9

    # 반려 흐름
    add_box(slide, Inches(3.5), Inches(2.5), Inches(2.5), Inches(0.5),
            "반려 → User 수정 후 재제출", fill_color=RGBColor(255, 230, 230),
            font_color=RED_ACCENT, font_size=9)

    # 결재 기능 표
    approval_data = [
        ["기능", "설명", "요구사항"],
        ["시수 결재 요청", "User가 주간 시수 작성 후 제출", "No.70"],
        ["승인/반려 + 코멘트", "DL/PM이 승인 또는 반려 (사유 필수)", "No.72"],
        ["일괄 승인", "여러 시수를 한번에 승인", "No.73"],
        ["결재 이력 조회", "과거 결재 내역 전체 조회", "No.74"],
        ["초과 하이라이트", "기준 초과 시 결재자에게 강조 표시", "No.75 (PH1-2)"],
        ["미완료 알림", "미입력/미제출 시수 자동 리마인더", "No.70"],
    ]
    add_table(slide, Inches(0.3), Inches(3.3), Inches(9.4),
              approval_data, col_widths=[Inches(2), Inches(5), Inches(1.5)])

    # 리포트
    slide = content_slide(prs, "05-4. 리포트")
    report_data = [
        ["리포트", "설명", "Phase", "필터"],
        ["프로젝트별 시수 분석", "프로젝트별 실제 투입 Manhour", "PH1-1", "기간, 프로젝트, Discipline"],
        ["WBS Level별 시수 분석", "Canonical WBS Level별 Manhour", "PH1-1", "기간, 프로젝트, WBS Level"],
        ["Phase별 시수 비율", "Engineering/Procurement 등 비율", "PH1-2", "기간, 프로젝트"],
        ["Non-Project 비율", "Overhead Manhour 투입 비율", "PH1-2", "기간, 부서"],
        ["WBS 버전 이력", "WBS 변경 이력 조회", "PH1-2", "프로젝트, 버전"],
        ["Discipline 생산성", "Discipline별 생산성 분석", "PH2", "기간, Discipline"],
    ]
    add_table(slide, Inches(0.3), Inches(1.0), Inches(9.4),
              report_data, col_widths=[Inches(2.2), Inches(3.5), Inches(1), Inches(2)])

    # ============================================================
    # 06. 데이터베이스 설계
    # ============================================================
    section_slide(prs, 6, "데이터베이스 설계", "ERD, 주요 테이블 14개, Flyway 마이그레이션")

    slide = content_slide(prs, "06-1. ERD 개요 (wtm_db)")

    # ERD 도식
    entities = [
        ("users", Inches(0.3), Inches(1.0), ["id, employee_number", "email, full_name", "department, discipline", "is_active, mfa_enabled"]),
        ("projects", Inches(3.3), Inches(1.0), ["id, project_code", "name, project_type", "status, pm_user_id"]),
        ("canonical_wbs", Inches(6.3), Inches(1.0), ["id, project_id", "wbs_code, level", "name, discipline"]),
        ("timesheets", Inches(0.3), Inches(3.2), ["id, user_id", "week_start_date", "status, total_hours"]),
        ("timesheet_entries", Inches(3.3), Inches(3.2), ["id, timesheet_id", "entry_type, entry_date", "hours, epc_project_id", "canonical_wbs_id"]),
        ("tt_approvals", Inches(6.3), Inches(3.2), ["id, timesheet_id", "requester_id, status", "submitted_at"]),
        ("tt_approval_lines", Inches(6.3), Inches(5.0), ["id, approval_id", "approver_id, order", "role_code, status"]),
        ("wbs_versions", Inches(3.3), Inches(5.0), ["id, project_id", "version_number", "effective_date"]),
    ]

    for name, x, y, fields in entities:
        h = Inches(0.2 + len(fields) * 0.2)
        add_multiline_box(slide, x, y, Inches(2.7), h, name, fields, fill_color=LIGHT_BG)

    # DB 테이블 목록
    slide = content_slide(prs, "06-2. 주요 테이블 목록")
    db_data = [
        ["테이블", "용도", "주요 컬럼"],
        ["users", "사용자 (HR 필드 포함)", "employee_number, business_unit~part (5레벨)"],
        ["projects", "프로젝트 (EPC/Other/Non)", "project_code, type, status, pm_user_id"],
        ["wbs_versions", "P6 WBS 스냅샷", "version_number, effective_date, source_filename"],
        ["wbs_nodes", "WBS 트리 (L1~L5)", "wbs_code, level, parent_id, planned_hours"],
        ["canonical_wbs", "정규 WBS", "wbs_code, mapped_p6_code, discipline"],
        ["teal_entries", "TEAL Activity", "canonical_wbs_id, activity_code, activity_name"],
        ["timesheets", "주간 시수 헤더", "user_id, week_start_date, status, total_hours"],
        ["timesheet_entries", "일별 시수 상세", "entry_type, entry_date, hours, remark"],
        ["tt_approvals", "결재 요청", "timesheet_id, requester_id, status"],
        ["tt_approval_lines", "결재 라인 (DL/PM)", "approver_id, approval_order, role_code"],
        ["work_rules", "근무 규칙", "location_code, min_daily, max_weekly"],
        ["overhead_types", "Overhead 유형", "code, name (SA 관리)"],
        ["project_assignments", "인력 배정", "project_id, user_id, discipline"],
    ]
    add_table(slide, Inches(0.2), Inches(1.0), Inches(9.6),
              db_data, col_widths=[Inches(2), Inches(2.5), Inches(4.5)])

    # ============================================================
    # 07. API 설계
    # ============================================================
    section_slide(prs, 7, "API 설계", "REST API 79개 — /api/wtm/ prefix")

    slide = content_slide(prs, "07. REST API 스펙 (79개, /api/wtm/)")
    api_data = [
        ["모듈", "PH1-1", "PH1-2", "PH2", "합계", "주요 Endpoint"],
        ["Auth", "8", "0", "0", "8", "/auth/login, /auth/sso, /auth/mfa/*"],
        ["Users", "7", "1", "0", "8", "/users, /users/upload/internal"],
        ["Projects", "7", "0", "0", "7", "/projects, /projects/my, /projects/{id}/members"],
        ["WBS", "6", "1", "0", "7", "/projects/{id}/wbs/upload, /canonical-wbs"],
        ["TEAL", "4", "0", "0", "4", "/projects/{id}/teal/active"],
        ["Timesheets", "8", "0", "0", "8", "/timesheets/week, /entries/batch, /upload"],
        ["Approvals", "7", "1", "0", "8", "/approvals/unified/action/{type}/{id}/*"],
        ["Reports", "4", "2", "3", "9", "/reports/project-hours, /wbs-hours"],
        ["Resource", "8", "0", "0", "8", "/projects/{id}/assignments"],
        ["Config", "4", "0", "0", "4", "/overhead-types, /work-rules"],
        ["Integration", "1", "1", "1", "3", "/integration/hr/sync, /cognite/export"],
        ["Notification", "3", "0", "0", "3", "/notifications/stream (SSE)"],
        ["합계", "67", "6", "6", "79", ""],
    ]
    add_table(slide, Inches(0.2), Inches(1.0), Inches(9.6),
              api_data, col_widths=[Inches(1.2), Inches(0.7), Inches(0.7), Inches(0.7), Inches(0.7), Inches(5)])

    # ============================================================
    # 08. 화면 구성
    # ============================================================
    section_slide(prs, 8, "화면 구성", "전체 49개 화면 — PH1-1차 26 / PH1-2차 11 / PH2 12")

    slide = content_slide(prs, "08. 전체 화면 목록 (49개)")
    screen_data = [
        ["그룹", "PH1-1차 (26개)", "PH1-2차 (11개)", "PH2 (12개)"],
        ["로그인/인증", "로그인, SSO, PW찾기, PW만료 (4)", "", ""],
        ["홈/권한", "역할별 홈 5종 (5)", "권한라우팅, SA로그 (2)", ""],
        ["사용자/인력", "내부/외주 업로드, 관리 (3)", "", "외주 개별입력 (1)"],
        ["프로젝트/WBS", "등록, P6, WBS, TEAL 등 (6)", "WBS비교, 시수조회 (3)", ""],
        ["시수 입력", "Non/Other/EPC 입력, Excel (4)", "EPC Revision (1)", "벤치마킹, Rate (2)"],
        ["결재", "결재요청, 목록, 일괄승인 (3)", "초과알림, MFA (2)", ""],
        ["리포트", "프로젝트별, WBS별 (2)", "Phase비율, NP비율 (2)", "RCP 3종, Discipline 등 (5)"],
        ["기타", "", "HR배치 (1)", "외주포털, Favorite 등 (4)"],
    ]
    add_table(slide, Inches(0.2), Inches(1.0), Inches(9.6),
              screen_data, col_widths=[Inches(1.5), Inches(3), Inches(2.5), Inches(2.5)])

    # ============================================================
    # 09. 인터페이스 설계
    # ============================================================
    section_slide(prs, 9, "인터페이스 설계", "SAP BTP (HR), P6 (WBS 파일), Cognite (Export)")

    slide = content_slide(prs, "09. 외부 시스템 인터페이스")

    # SAP BTP
    add_multiline_box(slide, Inches(0.3), Inches(1.0), Inches(2.5), Inches(2),
                     "SAP SuccessFactors", [
                         "HR Master Data",
                         "Employee Central",
                         "OData API"
                     ], fill_color=RGBColor(255, 245, 220))

    add_textbox(slide, Inches(2.8), Inches(1.7), Inches(0.8), Inches(0.3),
                "→ BTP →", font_size=10, color=ACCENT, bold=True, align=PP_ALIGN.CENTER)

    add_multiline_box(slide, Inches(3.6), Inches(1.0), Inches(2.5), Inches(2),
                     "SAP BTP (CPI)", [
                         "OData → REST 변환",
                         "필드 매핑",
                         "일 1회 배치 (PH1-2)",
                         "실시간 이벤트 (PH2)"
                     ], fill_color=RGBColor(240, 235, 255))

    add_textbox(slide, Inches(6.1), Inches(1.7), Inches(0.5), Inches(0.3),
                "→", font_size=14, color=ACCENT, bold=True, align=PP_ALIGN.CENTER)

    add_multiline_box(slide, Inches(6.6), Inches(1.0), Inches(3), Inches(2),
                     "WTM Spring Boot", [
                         "POST /api/wtm/integration/hr/sync",
                         "HrSyncService",
                         "사용자 자동 동기화",
                         "PH1-1: Excel 수동 업로드"
                     ], fill_color=RGBColor(230, 240, 255))

    # P6
    add_box(slide, Inches(0.3), Inches(3.5), Inches(2.5), Inches(0.7),
            "P6 (WBS Export)\n파일 기반 (No I/F)", fill_color=RGBColor(255, 245, 220),
            font_color=DARK, font_size=9)

    add_textbox(slide, Inches(2.8), Inches(3.65), Inches(1), Inches(0.3),
                "→ 파일 →", font_size=9, color=ACCENT, bold=True, align=PP_ALIGN.CENTER)

    add_box(slide, Inches(3.8), Inches(3.5), Inches(3), Inches(0.7),
            "WTM: P6 WBS 파서\nPM 업로드 → PCM 승인", fill_color=RGBColor(230, 240, 255),
            font_color=DARK, font_size=9)

    # Cognite
    add_box(slide, Inches(0.3), Inches(4.5), Inches(2.5), Inches(0.7),
            "Cognite (PH2)\nExtractor 서버", fill_color=RGBColor(255, 245, 220),
            font_color=DARK, font_size=9)

    add_textbox(slide, Inches(2.8), Inches(4.65), Inches(1), Inches(0.3),
                "← Export", font_size=9, color=ACCENT, bold=True, align=PP_ALIGN.CENTER)

    add_box(slide, Inches(3.8), Inches(4.5), Inches(5.5), Inches(0.7),
            "Export: Employee + Project + WBS + Time Fact + Mapping Version Metadata",
            fill_color=RGBColor(230, 240, 255), font_color=DARK, font_size=8)

    # ============================================================
    # 10. 보안 설계
    # ============================================================
    section_slide(prs, 10, "보안 설계", "한화오션 표준 보안 SW 12종, Azure Entra ID SSO + MFA")

    slide = content_slide(prs, "10. 한화오션 표준 보안 SW")
    sec_data = [
        ["분류", "SW", "용도", "구성"],
        ["서버 보안", "HIWARE", "서버 접근 제어", "Agent 설치"],
        ["서버 보안", "V3 (AhnLab)", "서버 백신", "Agent 설치"],
        ["서버 보안", "Secuver TOS", "파일 무결성", "Agent 설치"],
        ["DB 보안", "Cubeone", "DB 암호화 (컬럼)", "연동"],
        ["DB 보안", "Dbsafer", "DB 접근 제어/감사", "Proxy"],
        ["클라우드", "Azure Defender", "위협 탐지", "Native"],
        ["모니터링", "onTune", "SMS (서버)", "Agent"],
        ["모니터링", "Maxguage", "DB 모니터링", "Proxy"],
        ["모니터링", "Jennifer", "WAS APM", "-javaagent"],
        ["인증", "Azure Entra ID", "SSO + Conditional Access", "OAuth2 OIDC"],
        ["인증", "TOTP (MFA)", "외부사용자 2-Way 인증", "Google/MS Auth"],
    ]
    add_table(slide, Inches(0.3), Inches(1.0), Inches(9.4),
              sec_data, col_widths=[Inches(1.3), Inches(2), Inches(3), Inches(2.5)])

    # ============================================================
    # 11. 단계별 추진 일정
    # ============================================================
    section_slide(prs, 11, "단계별 추진 일정", "PH1-1차(9주) + PH1-2차(4주) = 15 M/M")

    slide = content_slide(prs, "11-1. 전체 일정 (Gantt)")

    # Gantt-like 도식
    months = ["4월", "5월", "6월", "7월~"]
    mx = 2.5
    for m in months:
        add_box(slide, Inches(mx), Inches(1.0), Inches(1.8), Inches(0.35),
                m, fill_color=PRIMARY, font_color=WHITE, font_size=10)
        mx += 1.8

    phases = [
        ("분석·설계", 2.5, 2.5, RGBColor(100, 160, 230)),
        ("BE 개발", 3.0, 5.5, RGBColor(70, 140, 200)),
        ("FE 개발", 3.5, 5.0, RGBColor(90, 170, 220)),
        ("QA·UAT", 6.5, 2.0, RGBColor(230, 160, 60)),
        ("PH1-2차", 6.1, 3.6, RGBColor(140, 200, 140)),
    ]
    gy = 1.6
    for name, start, width, color in phases:
        add_box(slide, Inches(start), Inches(gy), Inches(width), Inches(0.35),
                name, fill_color=color, font_color=WHITE, font_size=9)
        gy += 0.45

    # 마일스톤
    milestones = [
        ("4/10", "설계 확정"),
        ("5/7", "BE API 완료"),
        ("5/20", "FE 완료"),
        ("5/31", "★ PH1-1 오픈"),
        ("6/30", "★ PH1-2 오픈"),
    ]
    add_textbox(slide, Inches(0.5), Inches(4.0), Inches(9), Inches(0.3),
                "주요 마일스톤", font_size=12, color=PRIMARY, bold=True)
    my = 4.4
    for date, desc in milestones:
        add_textbox(slide, Inches(0.5), Inches(my), Inches(1.5), Inches(0.25),
                    date, font_size=10, color=ACCENT, bold=True)
        add_textbox(slide, Inches(2.0), Inches(my), Inches(7), Inches(0.25),
                    desc, font_size=10, color=DARK)
        my += 0.3

    # 인력 투입
    slide = content_slide(prs, "11-2. 인력 투입 계획")
    manpower = [
        ["역할", "PH1-1차 (9주)", "PH1-2차 (4주)", "담당"],
        ["BE 시니어 / 기술리드", "2.0 M/M", "1.0 M/M", "아키텍처, SSO, 결재, 코드리뷰"],
        ["풀스택 엔지니어 ①", "2.0 M/M", "1.0 M/M", "WBS, TEAL, 프로젝트"],
        ["풀스택 엔지니어 ②", "2.0 M/M", "1.0 M/M", "시수 입력 3종, 규칙 엔진"],
        ["풀스택 엔지니어 ③", "2.0 M/M", "1.0 M/M", "리포트, 로그인, 사용자"],
        ["DevOps (파트타임)", "1.0 M/M", "0.5 M/M", "Azure 인프라, CI/CD"],
        ["QA 겸임", "1.0 M/M", "0.5 M/M", "통합테스트, UAT 지원"],
        ["합계", "~10.0 M/M", "~5.0 M/M", "총 PH1: ~15 M/M"],
    ]
    add_table(slide, Inches(0.3), Inches(1.0), Inches(9.4),
              manpower, col_widths=[Inches(2.2), Inches(1.5), Inches(1.5), Inches(4)])

    # ============================================================
    # 마지막
    # ============================================================
    slide = prs.slides.add_slide(prs.slide_layouts[6])
    bg = slide.background.fill
    bg.solid()
    bg.fore_color.rgb = PRIMARY
    add_textbox(slide, Inches(1), Inches(2.5), Inches(8), Inches(1),
                "감사합니다", font_size=36, color=WHITE, bold=True, align=PP_ALIGN.CENTER)
    add_textbox(slide, Inches(1), Inches(3.8), Inches(8), Inches(0.5),
                "한화오션 EPU Time Tracking 시스템을\n함께 성공적으로 완수하겠습니다.",
                font_size=14, color=RGBColor(180, 200, 230), align=PP_ALIGN.CENTER)
    add_textbox(slide, Inches(1), Inches(5), Inches(8), Inches(0.5),
                "아큐라시스템 | accura@accurasoft.co.kr",
                font_size=12, color=RGBColor(150, 170, 200), align=PP_ALIGN.CENTER)

    prs.save(OUTPUT)
    size_kb = os.path.getsize(OUTPUT) // 1024
    try:
        print(f"Generated: {OUTPUT} ({size_kb} KB)")
    except UnicodeEncodeError:
        print(f"Generated: PPT ({size_kb} KB)")


if __name__ == "__main__":
    build()
