#!/usr/bin/env python3
"""Confluence MCP Server - 페이지 조회, 생성, 수정, 테이블/데이터베이스 핸들링

Confluence Cloud REST API v2를 사용합니다.
https://developer.atlassian.com/cloud/confluence/rest/v2/intro/

테이블 기능: 페이지 내 HTML 테이블 파싱, 수정, 생성
데이터베이스 기능: Confluence Database 메타데이터 조회/생성/삭제 (행 수준 API 미지원)
"""

import difflib
import json
import os
import re
import logging
import sys
from datetime import datetime
from pathlib import Path
from typing import Optional, Tuple, List, Dict, Any
from urllib.parse import urlparse, urljoin

import markdown
import requests
from bs4 import BeautifulSoup, Tag
from fastmcp import FastMCP

# 디버그 로깅 설정
logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[logging.StreamHandler(sys.stderr)]
)
logger = logging.getLogger(__name__)

# MCP 서버 초기화
mcp = FastMCP("confluence-mcp")

# 환경 변수에서 인증 정보 로드
CONFLUENCE_URL = os.environ.get("CONFLUENCE_URL")  # https://your-domain.atlassian.net
CONFLUENCE_EMAIL = os.environ.get("CONFLUENCE_EMAIL")
CONFLUENCE_API_TOKEN = os.environ.get("CONFLUENCE_API_TOKEN")

if not all([CONFLUENCE_URL, CONFLUENCE_EMAIL, CONFLUENCE_API_TOKEN]):
    raise ValueError(
        "필수 환경 변수가 설정되지 않았습니다: "
        "CONFLUENCE_URL, CONFLUENCE_EMAIL, CONFLUENCE_API_TOKEN"
    )

# Base URL 정리 (trailing slash 제거)
CONFLUENCE_URL = CONFLUENCE_URL.rstrip("/")
API_V2_BASE = f"{CONFLUENCE_URL}/wiki/api/v2"
API_V1_BASE = f"{CONFLUENCE_URL}/wiki/rest/api"


def _get_auth():
    """Basic Auth 튜플 반환"""
    return (CONFLUENCE_EMAIL, CONFLUENCE_API_TOKEN)


def _get_headers():
    """API 요청 헤더"""
    return {
        "Accept": "application/json",
        "Content-Type": "application/json",
    }


def _convert_image_to_storage(alt: str, src: str, extra_attrs: str = "") -> str:
    """마크다운 이미지를 Confluence ac:image 스토리지 포맷으로 변환

    Args:
        alt: 이미지 alt 텍스트
        src: 이미지 경로 또는 URL
        extra_attrs: '{width=700 height=400 version=1}' 형태의 추가 속성 문자열
    """
    # {width=700 height=400 version=1} 파싱
    width = ""
    height = ""
    version = ""
    if extra_attrs:
        w = re.search(r'width=(\d+)', extra_attrs)
        h = re.search(r'height=(\d+)', extra_attrs)
        v = re.search(r'version=(\d+)', extra_attrs)
        if w: width = w.group(1)
        if h: height = h.group(1)
        if v: version = v.group(1)

    # ac:image 속성 구성
    img_attrs = f'ac:alt="{alt}"'
    if width: img_attrs += f' ac:width="{width}"'
    if height: img_attrs += f' ac:height="{height}"'

    # 외부 URL인 경우 (http:// 또는 https://)
    if src.startswith("http://") or src.startswith("https://"):
        return f'<ac:image {img_attrs}><ri:url ri:value="{src}" /></ac:image>'
    # 로컬 첨부파일인 경우
    else:
        filename = src.split("/")[-1]
        version_attr = f' ri:version-at-save="{version}"' if version else ""
        return f'<ac:image {img_attrs}><ri:attachment ri:filename="{filename}"{version_attr} /></ac:image>'


def markdown_to_storage(md_content: str) -> str:
    """Markdown을 Confluence Storage Format으로 변환

    Confluence Storage Format은 XHTML 기반입니다.
    기본적인 Markdown 문법을 변환합니다.
    <!-- acimg:원본태그:acimg --> 주석이 있으면 원본 ac:image 태그를 복원합니다.
    """
    # 1단계: 원본 보존 마커가 있는 이미지를 플레이스홀더로 치환
    # ![alt](src)[acimg:base64:acimg] 패턴을 먼저 처리
    import base64 as _b64
    image_store = {}
    def _store_preserved_image(m):
        key = f"CFIMGPH{len(image_store)}PH"
        try:
            original_tag = _b64.b64decode(m.group(3)).decode('utf-8')
        except Exception:
            original_tag = ""
        image_store[key] = {
            'original_tag': original_tag,
            'type': 'preserved'
        }
        return key

    md_content = re.sub(
        r'!\[([^\]]*)\]\(([^)]+)\)\[acimg:([A-Za-z0-9+/=]+):acimg\]',
        _store_preserved_image,
        md_content
    )

    # 원본 보존 주석 없는 일반 이미지도 플레이스홀더로 치환
    def _store_image(m):
        key = f"CFIMGPH{len(image_store)}PH"
        image_store[key] = {
            'alt': m.group(1) or "",
            'src': m.group(2),
            'attrs': m.group(3) or "" if m.lastindex and m.lastindex >= 3 else "",
            'type': 'new'
        }
        return key

    md_content = re.sub(
        r'!\[([^\]]*)\]\(([^)]+)\)(?:\{([^}]+)\})?',
        _store_image,
        md_content
    )

    # 2단계: markdown 라이브러리로 기본 HTML 변환
    html = markdown.markdown(
        md_content,
        extensions=[
            "tables",
            "fenced_code",
            "nl2br",
            "sane_lists",
        ]
    )

    # 3단계: 플레이스홀더를 ac:image로 변환
    for key, img_data in image_store.items():
        if img_data.get('type') == 'preserved':
            # 원본 태그 복원 (속성 손실 없음)
            replacement = img_data['original_tag']
        else:
            # 새 이미지 — 기본 속성으로 생성
            replacement = _convert_image_to_storage(img_data['alt'], img_data['src'], img_data.get('attrs', ''))
        # <p> 태그 안에 감싸진 경우 먼저 처리
        html = html.replace(f'<p>{key}</p>', replacement)
        html = html.replace(key, replacement)

    # Confluence 특화 변환
    # 코드 블록을 Confluence 매크로로 변환
    html = re.sub(
        r'<pre><code class="language-(\w+)">(.*?)</code></pre>',
        r'<ac:structured-macro ac:name="code"><ac:parameter ac:name="language">\1</ac:parameter><ac:plain-text-body><![CDATA[\2]]></ac:plain-text-body></ac:structured-macro>',
        html,
        flags=re.DOTALL
    )

    # 일반 코드 블록 (언어 지정 없음)
    html = re.sub(
        r'<pre><code>(.*?)</code></pre>',
        r'<ac:structured-macro ac:name="code"><ac:plain-text-body><![CDATA[\1]]></ac:plain-text-body></ac:structured-macro>',
        html,
        flags=re.DOTALL
    )

    # 인라인 코드
    html = re.sub(
        r'<code>(.*?)</code>',
        r'<code>\1</code>',
        html
    )

    # 혹시 남은 <img> 태그 처리 (플레이스홀더로 안 잡힌 경우)
    def _convert_remaining_img(match):
        alt = match.group(1) or ""
        src = match.group(2)
        return _convert_image_to_storage(alt, src)

    html = re.sub(
        r'<img\s+alt="([^"]*)?"\s+src="([^"]+)"[^>]*/?>',
        _convert_remaining_img,
        html
    )
    html = re.sub(
        r'<img\s+src="([^"]+)"\s+alt="([^"]*)"[^>]*/?>',
        lambda m: _convert_image_to_storage(m.group(2), m.group(1)),
        html
    )
    html = re.sub(
        r'<img\s+src="([^"]+)"[^>]*/?>',
        lambda m: _convert_image_to_storage("", m.group(1)),
        html
    )

    return html


def storage_to_markdown(html_content: str) -> str:
    """Confluence Storage Format(HTML)을 Markdown으로 변환

    Args:
        html_content: Confluence Storage Format HTML

    Returns:
        Markdown 형식 문자열
    """
    if not html_content:
        return ""

    soup = BeautifulSoup(html_content, 'html.parser')

    # Confluence 매크로 처리
    # 코드 블록 매크로
    for macro in soup.find_all('ac:structured-macro', {'ac:name': 'code'}):
        lang_param = macro.find('ac:parameter', {'ac:name': 'language'})
        lang = lang_param.get_text() if lang_param else ''
        code_body = macro.find('ac:plain-text-body')
        code = code_body.get_text() if code_body else ''
        macro.replace_with(f'\n```{lang}\n{code}\n```\n')

    # 정보/노트/경고 매크로
    for macro in soup.find_all('ac:structured-macro', {'ac:name': ['info', 'note', 'warning', 'tip']}):
        macro_name = macro.get('ac:name', 'info')
        body = macro.find('ac:rich-text-body')
        content = body.get_text(strip=True) if body else ''
        emoji = {'info': 'ℹ️', 'note': '📝', 'warning': '⚠️', 'tip': '💡'}.get(macro_name, '')
        macro.replace_with(f'\n> {emoji} **{macro_name.upper()}**: {content}\n')

    # 테이블 패널 등 기타 매크로는 내용만 추출
    for macro in soup.find_all('ac:structured-macro'):
        body = macro.find(['ac:rich-text-body', 'ac:plain-text-body'])
        if body:
            macro.replace_with(body.get_text())
        else:
            macro.decompose()

    # 이미지 처리 (ac:image) — 원본 태그 전체를 보존하여 라운드트립 손실 방지
    # ac:image 태그에는 ac:align, ac:layout, ac:original-height/width, ac:custom-width,
    # ac:local-id, ac:width 등 Confluence 렌더링에 필수적인 속성이 포함됨.
    # 마크다운 변환 시 [acimg:base64원본:acimg] 형태로 보존 (HTML 이스케이프 방지).
    import base64 as _b64
    for img in soup.find_all('ac:image'):
        original_tag = str(img)
        encoded = _b64.b64encode(original_tag.encode('utf-8')).decode('ascii')
        attachment = img.find('ri:attachment')
        alt = img.get('ac:alt', '')

        if attachment:
            filename = attachment.get('ri:filename', 'image')
            display = alt if alt else filename
            img.replace_with(f'![{display}](images/{filename})[acimg:{encoded}:acimg]')
        else:
            url_tag = img.find('ri:url')
            if url_tag:
                url = url_tag.get('ri:value', '')
                display = alt if alt else 'image'
                img.replace_with(f'![{display}]({url})[acimg:{encoded}:acimg]')

    # 링크 처리
    for link in soup.find_all('ac:link'):
        page_ref = link.find('ri:page')
        if page_ref:
            title = page_ref.get('ri:content-title', 'link')
            link.replace_with(f'[{title}]')
        else:
            link_body = link.find('ac:link-body') or link.find('ac:plain-text-link-body')
            text = link_body.get_text() if link_body else 'link'
            link.replace_with(f'[{text}]')

    # 기본 HTML → Markdown 변환
    result = []

    def process_element(element, depth=0):
        if element.name is None:  # NavigableString
            text = str(element)
            if text.strip():
                result.append(text)
            return

        if element.name in ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']:
            level = int(element.name[1])
            result.append(f"\n{'#' * level} {element.get_text(strip=True)}\n")
        elif element.name == 'p':
            result.append(f"\n{element.get_text()}\n")
        elif element.name == 'br':
            result.append('\n')
        elif element.name == 'hr':
            result.append('\n---\n')
        elif element.name == 'strong' or element.name == 'b':
            result.append(f"**{element.get_text()}**")
        elif element.name == 'em' or element.name == 'i':
            result.append(f"*{element.get_text()}*")
        elif element.name == 'code':
            result.append(f"`{element.get_text()}`")
        elif element.name == 'pre':
            result.append(f"\n```\n{element.get_text()}\n```\n")
        elif element.name == 'ul':
            result.append('\n')
            for li in element.find_all('li', recursive=False):
                result.append(f"- {li.get_text(strip=True)}\n")
        elif element.name == 'ol':
            result.append('\n')
            for i, li in enumerate(element.find_all('li', recursive=False), 1):
                result.append(f"{i}. {li.get_text(strip=True)}\n")
        elif element.name == 'table':
            result.append('\n')
            rows = element.find_all('tr')
            for i, row in enumerate(rows):
                cells = row.find_all(['th', 'td'])
                row_text = ' | '.join(cell.get_text(strip=True) for cell in cells)
                result.append(f"| {row_text} |\n")
                if i == 0:  # 헤더 구분선
                    result.append(f"| {' | '.join(['---'] * len(cells))} |\n")
        elif element.name == 'a':
            href = element.get('href', '')
            text = element.get_text(strip=True)
            result.append(f"[{text}]({href})")
        elif element.name == 'img':
            src = element.get('src', '')
            alt = element.get('alt', 'image')
            result.append(f"![{alt}]({src})")
        elif element.name == 'blockquote':
            lines = element.get_text().strip().split('\n')
            for line in lines:
                result.append(f"> {line}\n")
        else:
            # 기타 태그는 자식 요소 처리
            for child in element.children:
                process_element(child, depth + 1)

    for child in soup.children:
        process_element(child)

    # 정리
    text = ''.join(result)
    # 연속 빈 줄 정리
    text = re.sub(r'\n{3,}', '\n\n', text)
    return text.strip()


# ============================================================
# 테이블 핸들링 헬퍼 함수
# ============================================================

def extract_tables_from_html(html_content: str) -> List[Dict[str, Any]]:
    """HTML에서 테이블 데이터 추출

    Args:
        html_content: Confluence Storage Format HTML

    Returns:
        테이블 정보 리스트 [{'index': int, 'headers': list, 'rows': list, 'row_count': int}]
    """
    if not html_content:
        return []

    soup = BeautifulSoup(html_content, 'html.parser')
    tables = []

    for idx, table in enumerate(soup.find_all('table')):
        headers = []
        rows = []

        # 헤더 추출 (thead 또는 첫 번째 tr의 th)
        thead = table.find('thead')
        if thead:
            header_row = thead.find('tr')
            if header_row:
                headers = [th.get_text(strip=True) for th in header_row.find_all(['th', 'td'])]
        else:
            # thead가 없으면 첫 번째 행에서 th 찾기
            first_row = table.find('tr')
            if first_row:
                ths = first_row.find_all('th')
                if ths:
                    headers = [th.get_text(strip=True) for th in ths]

        # 데이터 행 추출
        tbody = table.find('tbody') or table
        for tr in tbody.find_all('tr'):
            # 헤더 행 스킵
            if tr.find('th') and not tr.find('td'):
                if not headers:
                    headers = [th.get_text(strip=True) for th in tr.find_all('th')]
                continue

            cells = [td.get_text(strip=True) for td in tr.find_all(['td', 'th'])]
            if cells:
                rows.append(cells)

        tables.append({
            'index': idx,
            'headers': headers,
            'rows': rows,
            'row_count': len(rows),
            'col_count': len(headers) if headers else (len(rows[0]) if rows else 0)
        })

    return tables


def table_to_markdown(table_data: Dict[str, Any]) -> str:
    """테이블 데이터를 Markdown 형식으로 변환

    Args:
        table_data: {'headers': list, 'rows': list}

    Returns:
        Markdown 테이블 문자열
    """
    headers = table_data.get('headers', [])
    rows = table_data.get('rows', [])

    if not headers and not rows:
        return "(빈 테이블)"

    # 헤더가 없으면 컬럼 번호로 대체
    if not headers and rows:
        headers = [f"Col{i+1}" for i in range(len(rows[0]))]

    lines = []

    # 헤더 행
    lines.append("| " + " | ".join(headers) + " |")
    # 구분선
    lines.append("| " + " | ".join(["---"] * len(headers)) + " |")
    # 데이터 행
    for row in rows:
        # 컬럼 수 맞추기
        padded_row = row + [""] * (len(headers) - len(row))
        lines.append("| " + " | ".join(padded_row[:len(headers)]) + " |")

    return "\n".join(lines)


def dict_list_to_html_table(data: List[Dict], columns: Optional[List[str]] = None) -> str:
    """딕셔너리 리스트를 HTML 테이블로 변환

    Args:
        data: 딕셔너리 리스트 [{'col1': 'val1', 'col2': 'val2'}, ...]
        columns: 컬럼 순서 지정 (선택, 없으면 자동 추출)

    Returns:
        Confluence Storage Format HTML 테이블
    """
    if not data:
        return "<table><tbody><tr><td>데이터 없음</td></tr></tbody></table>"

    # 컬럼 추출
    if not columns:
        columns = list(data[0].keys())

    html_parts = ['<table>']

    # 헤더
    html_parts.append('<thead><tr>')
    for col in columns:
        html_parts.append(f'<th>{col}</th>')
    html_parts.append('</tr></thead>')

    # 데이터 행
    html_parts.append('<tbody>')
    for row in data:
        html_parts.append('<tr>')
        for col in columns:
            value = str(row.get(col, ''))
            html_parts.append(f'<td>{value}</td>')
        html_parts.append('</tr>')
    html_parts.append('</tbody>')

    html_parts.append('</table>')
    return ''.join(html_parts)


def list_to_html_table(headers: List[str], rows: List[List[str]]) -> str:
    """헤더와 행 리스트를 HTML 테이블로 변환

    Args:
        headers: 헤더 리스트
        rows: 2D 리스트 (행 데이터)

    Returns:
        Confluence Storage Format HTML 테이블
    """
    html_parts = ['<table>']

    # 헤더
    if headers:
        html_parts.append('<thead><tr>')
        for h in headers:
            html_parts.append(f'<th>{h}</th>')
        html_parts.append('</tr></thead>')

    # 데이터 행
    html_parts.append('<tbody>')
    for row in rows:
        html_parts.append('<tr>')
        for cell in row:
            html_parts.append(f'<td>{cell}</td>')
        html_parts.append('</tr>')
    html_parts.append('</tbody>')

    html_parts.append('</table>')
    return ''.join(html_parts)


def update_table_in_html(html_content: str, table_index: int, new_headers: Optional[List[str]], new_rows: List[List[str]]) -> str:
    """HTML 내 특정 테이블 업데이트

    Args:
        html_content: 기존 HTML
        table_index: 업데이트할 테이블 인덱스 (0부터)
        new_headers: 새 헤더 (None이면 기존 유지)
        new_rows: 새 데이터 행

    Returns:
        업데이트된 HTML
    """
    soup = BeautifulSoup(html_content, 'html.parser')
    tables = soup.find_all('table')

    if table_index >= len(tables):
        raise ValueError(f"테이블 인덱스 {table_index}가 범위를 벗어남 (총 {len(tables)}개)")

    target_table = tables[table_index]

    # 기존 헤더 가져오기 (new_headers가 None인 경우)
    if new_headers is None:
        existing_headers = []
        thead = target_table.find('thead')
        if thead:
            header_row = thead.find('tr')
            if header_row:
                existing_headers = [th.get_text(strip=True) for th in header_row.find_all(['th', 'td'])]
        if not existing_headers:
            first_row = target_table.find('tr')
            if first_row:
                ths = first_row.find_all('th')
                if ths:
                    existing_headers = [th.get_text(strip=True) for th in ths]
        new_headers = existing_headers

    # 새 테이블 생성
    new_table_html = list_to_html_table(new_headers, new_rows)
    new_table = BeautifulSoup(new_table_html, 'html.parser').find('table')

    # 기존 테이블 교체
    target_table.replace_with(new_table)

    return str(soup)


def add_row_to_table(html_content: str, table_index: int, row_data: List[str]) -> str:
    """HTML 내 특정 테이블에 행 추가

    Args:
        html_content: 기존 HTML
        table_index: 테이블 인덱스 (0부터)
        row_data: 추가할 행 데이터

    Returns:
        업데이트된 HTML
    """
    soup = BeautifulSoup(html_content, 'html.parser')
    tables = soup.find_all('table')

    if table_index >= len(tables):
        raise ValueError(f"테이블 인덱스 {table_index}가 범위를 벗어남 (총 {len(tables)}개)")

    target_table = tables[table_index]
    tbody = target_table.find('tbody')
    if not tbody:
        tbody = target_table

    # 새 행 생성
    new_row = soup.new_tag('tr')
    for cell_data in row_data:
        td = soup.new_tag('td')
        td.string = str(cell_data)
        new_row.append(td)

    tbody.append(new_row)

    return str(soup)


def delete_row_from_table(html_content: str, table_index: int, row_index: int) -> str:
    """HTML 내 특정 테이블에서 행 삭제

    Args:
        html_content: 기존 HTML
        table_index: 테이블 인덱스 (0부터)
        row_index: 삭제할 행 인덱스 (0부터, 헤더 제외)

    Returns:
        업데이트된 HTML
    """
    soup = BeautifulSoup(html_content, 'html.parser')
    tables = soup.find_all('table')

    if table_index >= len(tables):
        raise ValueError(f"테이블 인덱스 {table_index}가 범위를 벗어남 (총 {len(tables)}개)")

    target_table = tables[table_index]
    tbody = target_table.find('tbody') or target_table

    # 데이터 행만 추출 (th만 있는 행 제외)
    data_rows = [tr for tr in tbody.find_all('tr') if tr.find('td')]

    if row_index >= len(data_rows):
        raise ValueError(f"행 인덱스 {row_index}가 범위를 벗어남 (총 {len(data_rows)}개 행)")

    data_rows[row_index].decompose()

    return str(soup)


def update_cell_in_table(html_content: str, table_index: int, row_index: int, col_index: int, new_value: str) -> str:
    """HTML 내 특정 테이블의 셀 값 업데이트

    Args:
        html_content: 기존 HTML
        table_index: 테이블 인덱스 (0부터)
        row_index: 행 인덱스 (0부터, 헤더 제외)
        col_index: 열 인덱스 (0부터)
        new_value: 새 값

    Returns:
        업데이트된 HTML
    """
    soup = BeautifulSoup(html_content, 'html.parser')
    tables = soup.find_all('table')

    if table_index >= len(tables):
        raise ValueError(f"테이블 인덱스 {table_index}가 범위를 벗어남")

    target_table = tables[table_index]
    tbody = target_table.find('tbody') or target_table

    # 데이터 행만 추출
    data_rows = [tr for tr in tbody.find_all('tr') if tr.find('td')]

    if row_index >= len(data_rows):
        raise ValueError(f"행 인덱스 {row_index}가 범위를 벗어남")

    cells = data_rows[row_index].find_all('td')
    if col_index >= len(cells):
        raise ValueError(f"열 인덱스 {col_index}가 범위를 벗어남")

    cells[col_index].string = new_value

    return str(soup)


def extract_images_from_page(page_data: dict) -> List[dict]:
    """페이지에서 이미지 정보 추출

    Args:
        page_data: Confluence API 응답 데이터

    Returns:
        이미지 정보 리스트 [{'filename': str, 'url': str, 'type': 'attachment'|'external'}]
    """
    images = []
    body = page_data.get("body", {}).get("storage", {}).get("value", "")

    if not body:
        return images

    soup = BeautifulSoup(body, 'html.parser')

    # Confluence 첨부 이미지
    for img in soup.find_all('ac:image'):
        attachment = img.find('ri:attachment')
        if attachment:
            filename = attachment.get('ri:filename', '')
            if filename:
                images.append({
                    'filename': filename,
                    'type': 'attachment'
                })
        else:
            url_tag = img.find('ri:url')
            if url_tag:
                url = url_tag.get('ri:value', '')
                if url:
                    filename = url.split('/')[-1].split('?')[0] or 'external_image'
                    images.append({
                        'filename': filename,
                        'url': url,
                        'type': 'external'
                    })

    # 일반 HTML 이미지
    for img in soup.find_all('img'):
        src = img.get('src', '')
        if src:
            filename = src.split('/')[-1].split('?')[0] or 'image'
            images.append({
                'filename': filename,
                'url': src,
                'type': 'external'
            })

    return images


def get_page_attachments(page_id: str) -> List[dict]:
    """페이지의 모든 첨부파일 정보 조회

    Args:
        page_id: 페이지 ID

    Returns:
        첨부파일 정보 리스트 [{'filename': str, 'size': int, 'media_type': str, 'download_url': str}]
    """
    try:
        url = f"{API_V1_BASE}/content/{page_id}/child/attachment"
        response = requests.get(url, auth=_get_auth(), headers=_get_headers())
        response.raise_for_status()

        attachments = response.json().get("results", [])
        result = []

        for att in attachments:
            download_link = att.get('_links', {}).get('download', '')
            result.append({
                'filename': att.get('title', ''),
                'size': att.get('extensions', {}).get('fileSize', 0),
                'media_type': att.get('extensions', {}).get('mediaType', ''),
                'download_url': f"{CONFLUENCE_URL}/wiki{download_link}" if download_link else ''
            })

        return result

    except requests.RequestException:
        return []


def download_attachment(page_id: str, filename: str, save_path: str) -> Tuple[bool, str]:
    """Confluence 첨부파일 다운로드

    Args:
        page_id: 페이지 ID
        filename: 첨부파일 이름
        save_path: 저장 경로

    Returns:
        (성공 여부, 메시지)
    """
    try:
        # 첨부파일 목록 조회
        url = f"{API_V1_BASE}/content/{page_id}/child/attachment"
        response = requests.get(url, auth=_get_auth(), headers=_get_headers())
        response.raise_for_status()

        attachments = response.json().get("results", [])

        for att in attachments:
            if att.get("title") == filename:
                download_url = f"{CONFLUENCE_URL}/wiki{att.get('_links', {}).get('download', '')}"

                # 파일 다운로드
                file_response = requests.get(download_url, auth=_get_auth(), stream=True)
                file_response.raise_for_status()

                Path(save_path).parent.mkdir(parents=True, exist_ok=True)
                with open(save_path, 'wb') as f:
                    for chunk in file_response.iter_content(chunk_size=8192):
                        f.write(chunk)

                return True, f"다운로드 완료: {save_path}"

        return False, f"첨부파일을 찾을 수 없음: {filename}"

    except requests.RequestException as e:
        return False, f"다운로드 오류: {str(e)}"


def download_attachment_by_url(download_url: str, save_path: str) -> Tuple[bool, str]:
    """URL로 Confluence 첨부파일 다운로드

    Args:
        download_url: 다운로드 URL
        save_path: 저장 경로

    Returns:
        (성공 여부, 메시지)
    """
    try:
        file_response = requests.get(download_url, auth=_get_auth(), stream=True)
        file_response.raise_for_status()

        Path(save_path).parent.mkdir(parents=True, exist_ok=True)
        with open(save_path, 'wb') as f:
            for chunk in file_response.iter_content(chunk_size=8192):
                f.write(chunk)

        return True, f"다운로드 완료: {save_path}"

    except requests.RequestException as e:
        return False, f"다운로드 오류: {str(e)}"


def download_external_image(url: str, save_path: str) -> Tuple[bool, str]:
    """외부 이미지 다운로드

    Args:
        url: 이미지 URL
        save_path: 저장 경로

    Returns:
        (성공 여부, 메시지)
    """
    try:
        response = requests.get(url, stream=True, timeout=30)
        response.raise_for_status()

        Path(save_path).parent.mkdir(parents=True, exist_ok=True)
        with open(save_path, 'wb') as f:
            for chunk in response.iter_content(chunk_size=8192):
                f.write(chunk)

        return True, f"다운로드 완료: {save_path}"

    except requests.RequestException as e:
        return False, f"다운로드 오류: {str(e)}"


def extract_page_id_from_url(url: str) -> Optional[str]:
    """Confluence URL에서 페이지 ID 추출

    지원 URL 형식:
    - https://domain.atlassian.net/wiki/spaces/SPACE/pages/123456/Title
    - https://domain.atlassian.net/wiki/pages/viewpage.action?pageId=123456
    """
    # /pages/123456/ 패턴
    match = re.search(r'/pages/(\d+)', url)
    if match:
        return match.group(1)

    # pageId=123456 패턴
    match = re.search(r'pageId=(\d+)', url)
    if match:
        return match.group(1)

    return None


def _get_page_by_id(page_id: str) -> dict:
    """페이지 ID로 페이지 정보 조회"""
    url = f"{API_V2_BASE}/pages/{page_id}"
    params = {"body-format": "storage"}

    response = requests.get(url, auth=_get_auth(), headers=_get_headers(), params=params)
    response.raise_for_status()
    return response.json()


def _get_page_with_ancestors(page_id: str) -> dict:
    """페이지 ID로 페이지 정보 + 조상 정보 조회 (API v1)"""
    url = f"{API_V1_BASE}/content/{page_id}"
    params = {"expand": "ancestors,body.storage"}

    response = requests.get(url, auth=_get_auth(), headers=_get_headers(), params=params)
    response.raise_for_status()
    return response.json()


def _get_page_path(page_data: dict) -> str:
    """페이지의 계층 경로를 반환 (조상 페이지 제목 기반)

    Args:
        page_data: _get_page_with_ancestors로 가져온 페이지 데이터

    Returns:
        경로 문자열 (예: "회의록/Daily Scrum")
    """
    ancestors = page_data.get("ancestors", [])

    if not ancestors:
        return ""

    # 조상 페이지들의 제목으로 경로 구성 (루트 제외)
    path_parts = []
    for ancestor in ancestors:
        title = ancestor.get("title", "")
        if title:
            # 파일명으로 안전하게 변환
            safe_title = re.sub(r'[<>:"/\\|?*]', '_', title)
            safe_title = safe_title.strip()[:100]
            path_parts.append(safe_title)

    return "/".join(path_parts) if path_parts else ""


def _get_page_version(page_id: str) -> int:
    """페이지의 현재 버전 번호 조회"""
    page = _get_page_by_id(page_id)
    return page.get("version", {}).get("number", 1)


@mcp.tool()
def list_spaces(limit: int = 25) -> str:
    """접근 가능한 Confluence 공간(Space) 목록을 조회합니다.

    Args:
        limit: 조회할 공간 수 (기본값: 25, 최대: 100)

    Returns:
        공간 목록 (Key, 이름, 타입 포함)
    """
    try:
        limit = min(limit, 100)
        url = f"{API_V2_BASE}/spaces"
        params = {"limit": limit}

        response = requests.get(url, auth=_get_auth(), headers=_get_headers(), params=params)
        response.raise_for_status()

        data = response.json()
        spaces = data.get("results", [])

        if not spaces:
            return "접근 가능한 공간이 없습니다."

        output = []
        for space in spaces:
            space_type = space.get("type", "unknown")
            output.append(
                f"- **{space['name']}** (Key: `{space['key']}`)\n"
                f"  타입: {space_type} | ID: {space['id']}"
            )

        return f"## Confluence 공간 목록 ({len(spaces)}개)\n\n" + "\n\n".join(output)

    except requests.RequestException as e:
        return f"API 오류: {str(e)}"


@mcp.tool()
def get_page(page_id: Optional[str] = None, url: Optional[str] = None) -> str:
    """Confluence 페이지를 조회합니다.

    Args:
        page_id: 페이지 ID (선택)
        url: 페이지 URL (선택, page_id가 없을 때 사용)

    Returns:
        페이지 정보 및 내용
    """
    try:
        # URL에서 page_id 추출
        if not page_id and url:
            page_id = extract_page_id_from_url(url)
            if not page_id:
                return f"URL에서 페이지 ID를 추출할 수 없습니다: {url}"

        if not page_id:
            return "page_id 또는 url 중 하나는 필수입니다."

        page = _get_page_by_id(page_id)

        title = page.get("title", "제목 없음")
        space_id = page.get("spaceId", "N/A")
        version = page.get("version", {}).get("number", 1)
        body = page.get("body", {}).get("storage", {}).get("value", "내용 없음")

        # HTML 태그 간단히 정리 (가독성)
        body_preview = re.sub(r'<[^>]+>', ' ', body)[:1000]

        return (
            f"## 페이지 정보\n\n"
            f"- **제목**: {title}\n"
            f"- **페이지 ID**: `{page_id}`\n"
            f"- **Space ID**: {space_id}\n"
            f"- **버전**: {version}\n\n"
            f"### 내용 미리보기\n\n{body_preview}..."
        )

    except requests.HTTPError as e:
        if e.response.status_code == 404:
            return f"페이지를 찾을 수 없습니다: {page_id}"
        return f"API 오류: {e.response.status_code} - {e.response.text}"
    except requests.RequestException as e:
        return f"요청 오류: {str(e)}"


@mcp.tool()
def create_page(
    space_key: str,
    title: str,
    content: str,
    parent_id: Optional[str] = None,
    is_markdown: bool = True
) -> str:
    """새 Confluence 페이지를 생성합니다.

    Args:
        space_key: 공간 Key (예: DEV, TEAM)
        title: 페이지 제목
        content: 페이지 내용 (Markdown 또는 HTML)
        parent_id: 부모 페이지 ID (선택, 없으면 공간 루트에 생성)
        is_markdown: content가 Markdown인지 여부 (기본값: True)

    Returns:
        생성된 페이지 정보 및 URL
    """
    try:
        # Markdown → Storage Format 변환
        if is_markdown:
            body_content = markdown_to_storage(content)
        else:
            body_content = content

        # API v1 사용 (v2는 페이지 생성 API가 다름)
        url = f"{API_V1_BASE}/content"

        payload = {
            "type": "page",
            "title": title,
            "space": {"key": space_key},
            "body": {
                "storage": {
                    "value": body_content,
                    "representation": "storage"
                }
            }
        }

        if parent_id:
            payload["ancestors"] = [{"id": parent_id}]

        response = requests.post(
            url,
            auth=_get_auth(),
            headers=_get_headers(),
            json=payload
        )
        response.raise_for_status()

        result = response.json()
        page_id = result.get("id")
        page_url = f"{CONFLUENCE_URL}/wiki{result.get('_links', {}).get('webui', '')}"

        return (
            f"## 페이지 생성 완료\n\n"
            f"- **제목**: {title}\n"
            f"- **페이지 ID**: `{page_id}`\n"
            f"- **공간**: {space_key}\n"
            f"- **URL**: {page_url}"
        )

    except requests.HTTPError as e:
        error_msg = e.response.text
        if "title" in error_msg.lower() and "already exists" in error_msg.lower():
            return f"동일한 제목의 페이지가 이미 존재합니다: {title}"
        return f"API 오류: {e.response.status_code} - {error_msg}"
    except requests.RequestException as e:
        return f"요청 오류: {str(e)}"


@mcp.tool()
def update_page(
    page_id: Optional[str] = None,
    url: Optional[str] = None,
    title: Optional[str] = None,
    content: Optional[str] = None,
    is_markdown: bool = True
) -> str:
    """기존 Confluence 페이지를 수정합니다.

    Args:
        page_id: 페이지 ID (선택)
        url: 페이지 URL (선택, page_id가 없을 때 사용)
        title: 새 제목 (선택, 없으면 기존 제목 유지)
        content: 새 내용 (선택, 없으면 기존 내용 유지)
        is_markdown: content가 Markdown인지 여부 (기본값: True)

    Returns:
        수정된 페이지 정보
    """
    try:
        # 디버그: 수신된 파라미터 로깅
        logger.info(f"=== update_page 호출 ===")
        logger.info(f"  page_id: {page_id}")
        logger.info(f"  url: {url}")
        logger.info(f"  title: {title}")
        logger.info(f"  content length: {len(content) if content else 0}")
        logger.info(f"  content preview: {content[:200] if content else 'None'}...")
        logger.info(f"  is_markdown: {is_markdown}")

        # URL에서 page_id 추출
        if not page_id and url:
            page_id = extract_page_id_from_url(url)
            if not page_id:
                return f"URL에서 페이지 ID를 추출할 수 없습니다: {url}"

        if not page_id:
            return "page_id 또는 url 중 하나는 필수입니다."

        # 기존 페이지 정보 조회
        current_page = _get_page_by_id(page_id)
        current_version = current_page.get("version", {}).get("number", 1)
        current_title = current_page.get("title", "")

        logger.info(f"  현재 버전: {current_version}, 현재 제목: {current_title}")

        # 변경할 내용 결정
        new_title = title if title else current_title

        if content:
            if is_markdown:
                new_body = markdown_to_storage(content)
            else:
                new_body = content
            logger.info(f"  변환된 body 길이: {len(new_body)}")
        else:
            new_body = current_page.get("body", {}).get("storage", {}).get("value", "")
            logger.warning(f"  content가 없음! 기존 body 사용: {len(new_body)} chars")

        # API v1 사용 (v2 update API가 다름)
        api_url = f"{API_V1_BASE}/content/{page_id}"

        payload = {
            "id": page_id,
            "type": "page",
            "title": new_title,
            "version": {"number": current_version + 1},
            "body": {
                "storage": {
                    "value": new_body,
                    "representation": "storage"
                }
            }
        }

        response = requests.put(
            api_url,
            auth=_get_auth(),
            headers=_get_headers(),
            json=payload
        )
        response.raise_for_status()

        result = response.json()
        page_url = f"{CONFLUENCE_URL}/wiki{result.get('_links', {}).get('webui', '')}"
        new_version = result.get("version", {}).get("number", current_version + 1)

        return (
            f"## 페이지 수정 완료\n\n"
            f"- **제목**: {new_title}\n"
            f"- **페이지 ID**: `{page_id}`\n"
            f"- **버전**: {current_version} → {new_version}\n"
            f"- **URL**: {page_url}"
        )

    except requests.HTTPError as e:
        return f"API 오류: {e.response.status_code} - {e.response.text}"
    except requests.RequestException as e:
        return f"요청 오류: {str(e)}"


@mcp.tool()
def append_to_page(
    page_id: Optional[str] = None,
    url: Optional[str] = None,
    content: str = "",
    is_markdown: bool = True,
    add_separator: bool = True
) -> str:
    """기존 Confluence 페이지에 내용을 추가합니다.

    Args:
        page_id: 페이지 ID (선택)
        url: 페이지 URL (선택, page_id가 없을 때 사용)
        content: 추가할 내용 (Markdown 또는 HTML)
        is_markdown: content가 Markdown인지 여부 (기본값: True)
        add_separator: 기존 내용과 구분선 추가 여부 (기본값: True)

    Returns:
        수정된 페이지 정보
    """
    try:
        # URL에서 page_id 추출
        if not page_id and url:
            page_id = extract_page_id_from_url(url)
            if not page_id:
                return f"URL에서 페이지 ID를 추출할 수 없습니다: {url}"

        if not page_id:
            return "page_id 또는 url 중 하나는 필수입니다."

        if not content:
            return "추가할 content가 필요합니다."

        # 기존 페이지 정보 조회
        current_page = _get_page_by_id(page_id)
        current_version = current_page.get("version", {}).get("number", 1)
        current_title = current_page.get("title", "")
        current_body = current_page.get("body", {}).get("storage", {}).get("value", "")

        # 새 내용 변환
        if is_markdown:
            new_content = markdown_to_storage(content)
        else:
            new_content = content

        # 구분선 추가
        separator = "<hr/>" if add_separator else ""

        # 내용 병합
        merged_body = f"{current_body}{separator}{new_content}"

        # API v1 사용
        api_url = f"{API_V1_BASE}/content/{page_id}"

        payload = {
            "id": page_id,
            "type": "page",
            "title": current_title,
            "version": {"number": current_version + 1},
            "body": {
                "storage": {
                    "value": merged_body,
                    "representation": "storage"
                }
            }
        }

        response = requests.put(
            api_url,
            auth=_get_auth(),
            headers=_get_headers(),
            json=payload
        )
        response.raise_for_status()

        result = response.json()
        page_url = f"{CONFLUENCE_URL}/wiki{result.get('_links', {}).get('webui', '')}"
        new_version = result.get("version", {}).get("number", current_version + 1)

        return (
            f"## 페이지에 내용 추가 완료\n\n"
            f"- **제목**: {current_title}\n"
            f"- **페이지 ID**: `{page_id}`\n"
            f"- **버전**: {current_version} → {new_version}\n"
            f"- **URL**: {page_url}"
        )

    except requests.HTTPError as e:
        return f"API 오류: {e.response.status_code} - {e.response.text}"
    except requests.RequestException as e:
        return f"요청 오류: {str(e)}"


@mcp.tool()
def search_pages(
    query: str,
    space_key: Optional[str] = None,
    limit: int = 10
) -> str:
    """Confluence 페이지를 검색합니다.

    Args:
        query: 검색어
        space_key: 특정 공간으로 제한 (선택)
        limit: 결과 수 (기본값: 10, 최대: 50)

    Returns:
        검색 결과 목록
    """
    try:
        limit = min(limit, 50)

        # CQL(Confluence Query Language) 구성
        cql = f'type=page AND text~"{query}"'
        if space_key:
            cql += f' AND space="{space_key}"'

        url = f"{API_V1_BASE}/content/search"
        params = {
            "cql": cql,
            "limit": limit
        }

        response = requests.get(
            url,
            auth=_get_auth(),
            headers=_get_headers(),
            params=params
        )
        response.raise_for_status()

        data = response.json()
        results = data.get("results", [])

        if not results:
            return f"검색 결과가 없습니다: {query}"

        output = []
        for page in results:
            page_id = page.get("id")
            title = page.get("title", "제목 없음")
            space_name = page.get("space", {}).get("name", "N/A")
            page_url = f"{CONFLUENCE_URL}/wiki{page.get('_links', {}).get('webui', '')}"

            output.append(
                f"- **{title}**\n"
                f"  ID: `{page_id}` | 공간: {space_name}\n"
                f"  URL: {page_url}"
            )

        return f"## 검색 결과 ({len(results)}개)\n\n" + "\n\n".join(output)

    except requests.RequestException as e:
        return f"검색 오류: {str(e)}"


@mcp.tool()
def get_page_full(
    page_id: Optional[str] = None,
    url: Optional[str] = None,
    format: str = "markdown"
) -> str:
    """Confluence 페이지 전체 내용을 조회합니다.

    Args:
        page_id: 페이지 ID (선택)
        url: 페이지 URL (선택, page_id가 없을 때 사용)
        format: 출력 형식 - "markdown" 또는 "html" (기본값: markdown)

    Returns:
        페이지 전체 내용
    """
    try:
        # URL에서 page_id 추출
        if not page_id and url:
            page_id = extract_page_id_from_url(url)
            if not page_id:
                return f"URL에서 페이지 ID를 추출할 수 없습니다: {url}"

        if not page_id:
            return "page_id 또는 url 중 하나는 필수입니다."

        page = _get_page_by_id(page_id)

        title = page.get("title", "제목 없음")
        space_id = page.get("spaceId", "N/A")
        version = page.get("version", {}).get("number", 1)
        body_html = page.get("body", {}).get("storage", {}).get("value", "")

        # 이미지 정보 추출
        images = extract_images_from_page(page)
        image_info = ""
        if images:
            image_info = f"\n\n### 포함된 이미지 ({len(images)}개)\n"
            for img in images:
                image_info += f"- {img['filename']} ({img['type']})\n"

        if format.lower() == "html":
            body = body_html
        else:
            body = storage_to_markdown(body_html)

        return (
            f"## {title}\n\n"
            f"- **페이지 ID**: `{page_id}`\n"
            f"- **Space ID**: {space_id}\n"
            f"- **버전**: {version}\n"
            f"{image_info}\n"
            f"---\n\n"
            f"{body}"
        )

    except requests.HTTPError as e:
        if e.response.status_code == 404:
            return f"페이지를 찾을 수 없습니다: {page_id}"
        return f"API 오류: {e.response.status_code} - {e.response.text}"
    except requests.RequestException as e:
        return f"요청 오류: {str(e)}"


@mcp.tool()
def download_page_images(
    page_id: str,
    save_dir: str
) -> str:
    """Confluence 페이지의 이미지를 다운로드합니다.

    Args:
        page_id: 페이지 ID
        save_dir: 이미지 저장 디렉토리 경로

    Returns:
        다운로드 결과
    """
    try:
        page = _get_page_by_id(page_id)
        images = extract_images_from_page(page)

        if not images:
            return "페이지에 이미지가 없습니다."

        results = []
        success_count = 0
        fail_count = 0

        # 저장 디렉토리 생성
        save_path = Path(save_dir)
        save_path.mkdir(parents=True, exist_ok=True)

        for img in images:
            filename = img['filename']
            file_path = save_path / filename

            if img['type'] == 'attachment':
                success, msg = download_attachment(page_id, filename, str(file_path))
            else:
                url = img.get('url', '')
                if url:
                    success, msg = download_external_image(url, str(file_path))
                else:
                    success, msg = False, "URL 없음"

            if success:
                success_count += 1
                results.append(f"✓ {filename}")
            else:
                fail_count += 1
                results.append(f"✗ {filename}: {msg}")

        return (
            f"## 이미지 다운로드 결과\n\n"
            f"- **저장 위치**: {save_dir}\n"
            f"- **성공**: {success_count}개\n"
            f"- **실패**: {fail_count}개\n\n"
            f"### 상세\n" + "\n".join(results)
        )

    except requests.HTTPError as e:
        return f"API 오류: {e.response.status_code} - {e.response.text}"
    except Exception as e:
        return f"오류: {str(e)}"


@mcp.tool()
def download_page_attachments(
    page_id: str,
    save_dir: str,
    file_types: Optional[str] = None
) -> str:
    """Confluence 페이지의 모든 첨부파일을 다운로드합니다.

    Args:
        page_id: 페이지 ID
        save_dir: 첨부파일 저장 디렉토리 경로
        file_types: 다운로드할 파일 확장자 (쉼표 구분, 예: "pdf,xlsx,docx"). 미지정시 전체 다운로드

    Returns:
        다운로드 결과
    """
    try:
        attachments = get_page_attachments(page_id)

        if not attachments:
            return "페이지에 첨부파일이 없습니다."

        # 파일 타입 필터링
        if file_types:
            allowed_exts = [ext.strip().lower() for ext in file_types.split(',')]
            attachments = [
                att for att in attachments
                if any(att['filename'].lower().endswith(f'.{ext}') for ext in allowed_exts)
            ]
            if not attachments:
                return f"지정한 파일 타입({file_types})의 첨부파일이 없습니다."

        results = []
        success_count = 0
        fail_count = 0
        total_size = 0

        # 저장 디렉토리 생성
        save_path = Path(save_dir)
        save_path.mkdir(parents=True, exist_ok=True)

        for att in attachments:
            filename = att['filename']
            download_url = att['download_url']
            file_size = att['size']
            file_path = save_path / filename

            if download_url:
                success, msg = download_attachment_by_url(download_url, str(file_path))
            else:
                success, msg = False, "다운로드 URL 없음"

            if success:
                success_count += 1
                total_size += file_size
                size_str = f"{file_size / 1024:.1f}KB" if file_size < 1024 * 1024 else f"{file_size / 1024 / 1024:.1f}MB"
                results.append(f"✓ {filename} ({size_str})")
            else:
                fail_count += 1
                results.append(f"✗ {filename}: {msg}")

        total_size_str = f"{total_size / 1024:.1f}KB" if total_size < 1024 * 1024 else f"{total_size / 1024 / 1024:.1f}MB"

        return (
            f"## 첨부파일 다운로드 결과\n\n"
            f"- **저장 위치**: {save_dir}\n"
            f"- **성공**: {success_count}개 ({total_size_str})\n"
            f"- **실패**: {fail_count}개\n\n"
            f"### 상세\n" + "\n".join(results)
        )

    except requests.HTTPError as e:
        return f"API 오류: {e.response.status_code} - {e.response.text}"
    except Exception as e:
        return f"오류: {str(e)}"


@mcp.tool()
def export_page(
    page_id: Optional[str] = None,
    url: Optional[str] = None,
    save_dir: str = ".",
    include_images: bool = True,
    include_attachments: bool = False
) -> str:
    """Confluence 페이지를 Markdown 파일로 내보냅니다.

    Args:
        page_id: 페이지 ID (선택)
        url: 페이지 URL (선택, page_id가 없을 때 사용)
        save_dir: 저장 디렉토리 경로 (기본값: 현재 디렉토리)
        include_images: 이미지 다운로드 포함 여부 (기본값: True)
        include_attachments: 첨부파일(PDF 등) 다운로드 포함 여부 (기본값: False)

    Returns:
        내보내기 결과
    """
    try:
        # URL에서 page_id 추출
        if not page_id and url:
            page_id = extract_page_id_from_url(url)
            if not page_id:
                return f"URL에서 페이지 ID를 추출할 수 없습니다: {url}"

        if not page_id:
            return "page_id 또는 url 중 하나는 필수입니다."

        page = _get_page_by_id(page_id)

        title = page.get("title", "제목 없음")
        body_html = page.get("body", {}).get("storage", {}).get("value", "")

        # 파일명 안전하게 변환
        safe_title = re.sub(r'[<>:"/\\|?*]', '_', title)
        safe_title = safe_title.strip()[:100]  # 길이 제한

        # 저장 경로 설정
        save_path = Path(save_dir)
        save_path.mkdir(parents=True, exist_ok=True)

        # Markdown 변환
        markdown_content = storage_to_markdown(body_html)

        # 프론트매터 추가
        frontmatter = (
            f"---\n"
            f"title: \"{title}\"\n"
            f"page_id: \"{page_id}\"\n"
            f"source: confluence\n"
            f"---\n\n"
        )

        # Markdown 파일 저장
        md_file = save_path / f"{safe_title}.md"
        with open(md_file, 'w', encoding='utf-8') as f:
            f.write(frontmatter + markdown_content)

        result = (
            f"## 페이지 내보내기 완료\n\n"
            f"- **제목**: {title}\n"
            f"- **Markdown 파일**: {md_file}\n"
        )

        # 이미지 다운로드
        if include_images:
            images = extract_images_from_page(page)
            if images:
                images_dir = save_path / "images"
                images_dir.mkdir(parents=True, exist_ok=True)

                success_count = 0
                fail_count = 0

                for img in images:
                    filename = img['filename']
                    file_path = images_dir / filename

                    if img['type'] == 'attachment':
                        success, _ = download_attachment(page_id, filename, str(file_path))
                    else:
                        img_url = img.get('url', '')
                        if img_url:
                            success, _ = download_external_image(img_url, str(file_path))
                        else:
                            success = False

                    if success:
                        success_count += 1
                    else:
                        fail_count += 1

                result += (
                    f"- **이미지 폴더**: {images_dir}\n"
                    f"- **이미지 다운로드**: {success_count}개 성공, {fail_count}개 실패\n"
                )
            else:
                result += "- **이미지**: 없음\n"

        # 첨부파일 다운로드
        if include_attachments:
            attachments = get_page_attachments(page_id)
            # 이미지 파일은 제외 (이미 include_images에서 처리)
            non_image_attachments = [
                att for att in attachments
                if not any(att['filename'].lower().endswith(ext) for ext in ['.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp', '.svg'])
            ]

            if non_image_attachments:
                attachments_dir = save_path / "attachments"
                attachments_dir.mkdir(parents=True, exist_ok=True)

                att_success = 0
                att_fail = 0
                total_size = 0

                for att in non_image_attachments:
                    filename = att['filename']
                    download_url = att['download_url']
                    file_size = att['size']
                    file_path = attachments_dir / filename

                    if download_url:
                        success, _ = download_attachment_by_url(download_url, str(file_path))
                    else:
                        success = False

                    if success:
                        att_success += 1
                        total_size += file_size
                    else:
                        att_fail += 1

                size_str = f"{total_size / 1024:.1f}KB" if total_size < 1024 * 1024 else f"{total_size / 1024 / 1024:.1f}MB"
                result += (
                    f"- **첨부파일 폴더**: {attachments_dir}\n"
                    f"- **첨부파일 다운로드**: {att_success}개 성공 ({size_str}), {att_fail}개 실패\n"
                )
            else:
                result += "- **첨부파일**: 없음\n"

        return result

    except requests.HTTPError as e:
        if e.response.status_code == 404:
            return f"페이지를 찾을 수 없습니다: {page_id}"
        return f"API 오류: {e.response.status_code} - {e.response.text}"
    except Exception as e:
        return f"오류: {str(e)}"


@mcp.tool()
def export_space(
    space_key: str,
    save_dir: str,
    include_images: bool = True,
    include_attachments: bool = False,
    preserve_hierarchy: bool = False,
    limit: int = 50
) -> str:
    """Confluence 스페이스의 모든 페이지를 Markdown으로 내보냅니다.

    Args:
        space_key: 스페이스 Key (예: FU, DEV)
        save_dir: 저장 디렉토리 경로
        include_images: 이미지 다운로드 포함 여부 (기본값: True)
        include_attachments: 첨부파일(PDF 등) 다운로드 포함 여부 (기본값: False)
        preserve_hierarchy: Confluence 페이지 계층 구조를 폴더로 유지 (기본값: False)
        limit: 최대 페이지 수 (기본값: 50, 최대: 100)

    Returns:
        내보내기 결과
    """
    try:
        limit = min(limit, 100)

        # 스페이스의 페이지 목록 조회
        cql = f'type=page AND space="{space_key}"'
        url = f"{API_V1_BASE}/content/search"
        params = {"cql": cql, "limit": limit}

        response = requests.get(url, auth=_get_auth(), headers=_get_headers(), params=params)
        response.raise_for_status()

        pages = response.json().get("results", [])

        if not pages:
            return f"스페이스에 페이지가 없습니다: {space_key}"

        # 저장 디렉토리 생성
        save_path = Path(save_dir)
        save_path.mkdir(parents=True, exist_ok=True)

        results = []
        success_count = 0
        fail_count = 0

        for page in pages:
            page_id = page.get("id")
            title = page.get("title", "제목 없음")

            try:
                # 계층 구조 유지 옵션에 따라 다른 API 사용
                if preserve_hierarchy:
                    full_page = _get_page_with_ancestors(page_id)
                    body_html = full_page.get("body", {}).get("storage", {}).get("value", "")
                    page_path = _get_page_path(full_page)
                else:
                    full_page = _get_page_by_id(page_id)
                    body_html = full_page.get("body", {}).get("storage", {}).get("value", "")
                    page_path = ""

                # 파일명 안전하게 변환
                safe_title = re.sub(r'[<>:"/\\|?*]', '_', title)
                safe_title = safe_title.strip()[:100]

                # Markdown 변환
                markdown_content = storage_to_markdown(body_html)

                # 저장 경로 결정
                if preserve_hierarchy and page_path:
                    page_dir = save_path / page_path
                    page_dir.mkdir(parents=True, exist_ok=True)
                    md_file = page_dir / f"{safe_title}.md"
                    relative_path = f"{page_path}/{safe_title}.md"
                else:
                    md_file = save_path / f"{safe_title}.md"
                    relative_path = f"{safe_title}.md"

                # 프론트매터
                frontmatter = (
                    f"---\n"
                    f"title: \"{title}\"\n"
                    f"page_id: \"{page_id}\"\n"
                    f"source: confluence\n"
                    f"space: \"{space_key}\"\n"
                )
                if preserve_hierarchy and page_path:
                    frontmatter += f"path: \"{page_path}\"\n"
                frontmatter += f"---\n\n"

                # 저장
                with open(md_file, 'w', encoding='utf-8') as f:
                    f.write(frontmatter + markdown_content)

                # 이미지 다운로드
                if include_images:
                    # API v1과 v2의 body 구조가 다름
                    if preserve_hierarchy:
                        # v1 API 응답을 v2 형식으로 변환
                        page_for_images = {
                            "body": {"storage": {"value": body_html}}
                        }
                    else:
                        page_for_images = full_page

                    images = extract_images_from_page(page_for_images)
                    if images:
                        # 계층 구조면 해당 폴더 내에 images 생성
                        if preserve_hierarchy and page_path:
                            images_dir = save_path / page_path / "images"
                        else:
                            images_dir = save_path / "images"
                        images_dir.mkdir(parents=True, exist_ok=True)

                        for img in images:
                            filename = img['filename']
                            file_path = images_dir / filename

                            if img['type'] == 'attachment':
                                download_attachment(page_id, filename, str(file_path))
                            else:
                                img_url = img.get('url', '')
                                if img_url:
                                    download_external_image(img_url, str(file_path))

                # 첨부파일 다운로드
                if include_attachments:
                    attachments = get_page_attachments(page_id)
                    # 이미지 파일 제외
                    non_image_attachments = [
                        att for att in attachments
                        if not any(att['filename'].lower().endswith(ext) for ext in ['.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp', '.svg'])
                    ]

                    if non_image_attachments:
                        if preserve_hierarchy and page_path:
                            attachments_dir = save_path / page_path / "attachments"
                        else:
                            attachments_dir = save_path / "attachments"
                        attachments_dir.mkdir(parents=True, exist_ok=True)

                        for att in non_image_attachments:
                            filename = att['filename']
                            download_url = att['download_url']
                            file_path = attachments_dir / filename

                            if download_url:
                                download_attachment_by_url(download_url, str(file_path))

                success_count += 1
                results.append(f"✓ {relative_path}")

            except Exception as e:
                fail_count += 1
                results.append(f"✗ {title}: {str(e)}")

        hierarchy_info = "- **계층 구조 유지**: 예\n" if preserve_hierarchy else ""
        return (
            f"## 스페이스 내보내기 완료\n\n"
            f"- **스페이스**: {space_key}\n"
            f"- **저장 위치**: {save_dir}\n"
            f"{hierarchy_info}"
            f"- **성공**: {success_count}개\n"
            f"- **실패**: {fail_count}개\n\n"
            f"### 상세\n" + "\n".join(results)
        )

    except requests.RequestException as e:
        return f"API 오류: {str(e)}"
    except Exception as e:
        return f"오류: {str(e)}"


def _load_sync_metadata(save_dir: str) -> dict:
    """동기화 메타데이터 파일 로드

    Args:
        save_dir: 저장 디렉토리 경로

    Returns:
        메타데이터 딕셔너리 {page_id: {version, title, path, last_synced}}
    """
    metadata_file = Path(save_dir) / ".sync_metadata.json"
    if metadata_file.exists():
        with open(metadata_file, 'r', encoding='utf-8') as f:
            return json.load(f)
    return {"pages": {}, "last_full_sync": None}


def _save_sync_metadata(save_dir: str, metadata: dict):
    """동기화 메타데이터 파일 저장

    Args:
        save_dir: 저장 디렉토리 경로
        metadata: 메타데이터 딕셔너리
    """
    metadata_file = Path(save_dir) / ".sync_metadata.json"
    with open(metadata_file, 'w', encoding='utf-8') as f:
        json.dump(metadata, f, ensure_ascii=False, indent=2)


@mcp.tool()
def sync_space(
    space_key: str,
    save_dir: str,
    include_images: bool = True,
    include_attachments: bool = False,
    preserve_hierarchy: bool = True,
    force_full: bool = False,
    limit: int = 100
) -> str:
    """Confluence 스페이스를 동기화합니다 (변경된 문서만 업데이트).

    Args:
        space_key: 스페이스 Key (예: FU, DEV)
        save_dir: 저장 디렉토리 경로
        include_images: 이미지 다운로드 포함 여부 (기본값: True)
        include_attachments: 첨부파일(PDF 등) 다운로드 포함 여부 (기본값: False)
        preserve_hierarchy: Confluence 페이지 계층 구조를 폴더로 유지 (기본값: True)
        force_full: 전체 새로고침 강제 실행 (기본값: False)
        limit: 최대 페이지 수 (기본값: 100)

    Returns:
        동기화 결과
    """
    try:
        limit = min(limit, 100)

        # 메타데이터 로드
        metadata = _load_sync_metadata(save_dir)
        existing_pages = metadata.get("pages", {})

        # 스페이스의 페이지 목록 조회 (버전 정보 포함)
        cql = f'type=page AND space="{space_key}"'
        url = f"{API_V1_BASE}/content/search"
        params = {"cql": cql, "limit": limit, "expand": "version"}

        response = requests.get(url, auth=_get_auth(), headers=_get_headers(), params=params)
        response.raise_for_status()

        pages = response.json().get("results", [])

        if not pages:
            return f"스페이스에 페이지가 없습니다: {space_key}"

        # 저장 디렉토리 생성
        save_path = Path(save_dir)
        save_path.mkdir(parents=True, exist_ok=True)

        # 변경 감지
        pages_to_update = []
        pages_to_skip = []
        new_pages = []
        deleted_page_ids = set(existing_pages.keys())

        for page in pages:
            page_id = page.get("id")
            title = page.get("title", "제목 없음")
            current_version = page.get("version", {}).get("number", 1)

            deleted_page_ids.discard(page_id)

            if force_full:
                pages_to_update.append(page)
            elif page_id not in existing_pages:
                new_pages.append(page)
                pages_to_update.append(page)
            elif existing_pages[page_id].get("version") != current_version:
                pages_to_update.append(page)
            else:
                pages_to_skip.append(title)

        results = []
        updated_count = 0
        skipped_count = len(pages_to_skip)
        failed_count = 0
        new_metadata_pages = {}

        # 변경된 페이지만 업데이트
        for page in pages_to_update:
            page_id = page.get("id")
            title = page.get("title", "제목 없음")
            current_version = page.get("version", {}).get("number", 1)

            try:
                # 계층 구조 유지 옵션에 따라 다른 API 사용
                if preserve_hierarchy:
                    full_page = _get_page_with_ancestors(page_id)
                    body_html = full_page.get("body", {}).get("storage", {}).get("value", "")
                    page_path = _get_page_path(full_page)
                else:
                    full_page = _get_page_by_id(page_id)
                    body_html = full_page.get("body", {}).get("storage", {}).get("value", "")
                    page_path = ""

                # 파일명 안전하게 변환
                safe_title = re.sub(r'[<>:"/\\|?*]', '_', title)
                safe_title = safe_title.strip()[:100]

                # Markdown 변환
                markdown_content = storage_to_markdown(body_html)

                # 저장 경로 결정
                if preserve_hierarchy and page_path:
                    page_dir = save_path / page_path
                    page_dir.mkdir(parents=True, exist_ok=True)
                    md_file = page_dir / f"{safe_title}.md"
                    relative_path = f"{page_path}/{safe_title}.md"
                else:
                    md_file = save_path / f"{safe_title}.md"
                    relative_path = f"{safe_title}.md"

                # 프론트매터
                frontmatter = (
                    f"---\n"
                    f"title: \"{title}\"\n"
                    f"page_id: \"{page_id}\"\n"
                    f"version: {current_version}\n"
                    f"source: confluence\n"
                    f"space: \"{space_key}\"\n"
                )
                if preserve_hierarchy and page_path:
                    frontmatter += f"path: \"{page_path}\"\n"
                frontmatter += f"---\n\n"

                # 저장
                with open(md_file, 'w', encoding='utf-8') as f:
                    f.write(frontmatter + markdown_content)

                # 이미지 다운로드
                if include_images:
                    if preserve_hierarchy:
                        page_for_images = {"body": {"storage": {"value": body_html}}}
                    else:
                        page_for_images = full_page

                    images = extract_images_from_page(page_for_images)
                    if images:
                        if preserve_hierarchy and page_path:
                            images_dir = save_path / page_path / "images"
                        else:
                            images_dir = save_path / "images"
                        images_dir.mkdir(parents=True, exist_ok=True)

                        for img in images:
                            filename = img['filename']
                            file_path = images_dir / filename

                            if img['type'] == 'attachment':
                                download_attachment(page_id, filename, str(file_path))
                            else:
                                img_url = img.get('url', '')
                                if img_url:
                                    download_external_image(img_url, str(file_path))

                # 첨부파일 다운로드
                if include_attachments:
                    attachments = get_page_attachments(page_id)
                    # 이미지 파일 제외
                    non_image_attachments = [
                        att for att in attachments
                        if not any(att['filename'].lower().endswith(ext) for ext in ['.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp', '.svg'])
                    ]

                    if non_image_attachments:
                        if preserve_hierarchy and page_path:
                            attachments_dir = save_path / page_path / "attachments"
                        else:
                            attachments_dir = save_path / "attachments"
                        attachments_dir.mkdir(parents=True, exist_ok=True)

                        for att in non_image_attachments:
                            filename = att['filename']
                            download_url = att['download_url']
                            file_path = attachments_dir / filename

                            if download_url:
                                download_attachment_by_url(download_url, str(file_path))

                # 메타데이터 업데이트
                is_new = page_id not in existing_pages
                new_metadata_pages[page_id] = {
                    "version": current_version,
                    "title": title,
                    "path": relative_path,
                    "last_synced": datetime.now().isoformat()
                }

                updated_count += 1
                status = "🆕" if is_new else "🔄"
                results.append(f"{status} {relative_path} (v{current_version})")

            except Exception as e:
                failed_count += 1
                results.append(f"✗ {title}: {str(e)}")

        # 변경되지 않은 페이지 메타데이터 유지
        for page in pages:
            page_id = page.get("id")
            if page_id not in new_metadata_pages and page_id in existing_pages:
                new_metadata_pages[page_id] = existing_pages[page_id]

        # 삭제된 페이지 처리 (선택적)
        deleted_files = []
        for deleted_id in deleted_page_ids:
            if deleted_id in existing_pages:
                deleted_path = existing_pages[deleted_id].get("path", "")
                deleted_files.append(deleted_path)

        # 메타데이터 저장
        metadata["pages"] = new_metadata_pages
        metadata["last_sync"] = datetime.now().isoformat()
        metadata["space_key"] = space_key
        if force_full:
            metadata["last_full_sync"] = datetime.now().isoformat()
        _save_sync_metadata(save_dir, metadata)

        # 결과 출력
        sync_type = "전체 새로고침" if force_full else "증분 동기화"
        output = (
            f"## {sync_type} 완료\n\n"
            f"- **스페이스**: {space_key}\n"
            f"- **저장 위치**: {save_dir}\n"
            f"- **업데이트**: {updated_count}개\n"
            f"- **스킵 (변경 없음)**: {skipped_count}개\n"
            f"- **실패**: {failed_count}개\n"
        )

        if deleted_files:
            output += f"- **Confluence에서 삭제됨**: {len(deleted_files)}개\n"

        if results:
            output += f"\n### 변경 내역\n" + "\n".join(results)

        if deleted_files:
            output += f"\n\n### 삭제된 페이지 (로컬 파일 유지됨)\n"
            for df in deleted_files:
                output += f"- {df}\n"

        return output

    except requests.RequestException as e:
        return f"API 오류: {str(e)}"
    except Exception as e:
        return f"오류: {str(e)}"


# ============================================================
# 테이블 핸들링 MCP Tools
# ============================================================

@mcp.tool()
def get_page_tables(
    page_id: Optional[str] = None,
    url: Optional[str] = None,
    table_index: Optional[int] = None,
    output_format: str = "markdown"
) -> str:
    """페이지 내 테이블 데이터를 조회합니다.

    Args:
        page_id: 페이지 ID (선택)
        url: 페이지 URL (선택, page_id가 없을 때 사용)
        table_index: 특정 테이블만 조회 (0부터 시작, 선택)
        output_format: 출력 형식 - "markdown", "json", "csv" (기본값: markdown)

    Returns:
        테이블 데이터 (표 형식)
    """
    try:
        # URL에서 page_id 추출
        if not page_id and url:
            page_id = extract_page_id_from_url(url)
            if not page_id:
                return f"URL에서 페이지 ID를 추출할 수 없습니다: {url}"

        if not page_id:
            return "page_id 또는 url 중 하나는 필수입니다."

        page = _get_page_by_id(page_id)
        title = page.get("title", "제목 없음")
        body_html = page.get("body", {}).get("storage", {}).get("value", "")

        tables = extract_tables_from_html(body_html)

        if not tables:
            return f"페이지 '{title}'에 테이블이 없습니다."

        # 특정 테이블만 조회
        if table_index is not None:
            if table_index >= len(tables):
                return f"테이블 인덱스 {table_index}가 범위를 벗어남 (총 {len(tables)}개)"
            tables = [tables[table_index]]

        # 출력 형식에 따라 변환
        output = f"## 페이지: {title}\n\n"

        for tbl in tables:
            output += f"### 테이블 #{tbl['index']} ({tbl['row_count']}행 x {tbl['col_count']}열)\n\n"

            if output_format.lower() == "json":
                # JSON 형식
                if tbl['headers']:
                    json_data = []
                    for row in tbl['rows']:
                        row_dict = {}
                        for i, h in enumerate(tbl['headers']):
                            row_dict[h] = row[i] if i < len(row) else ""
                        json_data.append(row_dict)
                    output += f"```json\n{json.dumps(json_data, ensure_ascii=False, indent=2)}\n```\n\n"
                else:
                    output += f"```json\n{json.dumps(tbl['rows'], ensure_ascii=False, indent=2)}\n```\n\n"

            elif output_format.lower() == "csv":
                # CSV 형식
                lines = []
                if tbl['headers']:
                    lines.append(",".join(f'"{h}"' for h in tbl['headers']))
                for row in tbl['rows']:
                    lines.append(",".join(f'"{c}"' for c in row))
                output += f"```csv\n" + "\n".join(lines) + "\n```\n\n"

            else:
                # Markdown 형식 (기본)
                output += table_to_markdown(tbl) + "\n\n"

        return output

    except requests.HTTPError as e:
        if e.response.status_code == 404:
            return f"페이지를 찾을 수 없습니다: {page_id}"
        return f"API 오류: {e.response.status_code} - {e.response.text}"
    except Exception as e:
        return f"오류: {str(e)}"


@mcp.tool()
def update_page_table(
    page_id: Optional[str] = None,
    url: Optional[str] = None,
    table_index: int = 0,
    headers: Optional[str] = None,
    rows: str = "[]"
) -> str:
    """페이지 내 특정 테이블을 업데이트합니다.

    Args:
        page_id: 페이지 ID (선택)
        url: 페이지 URL (선택, page_id가 없을 때 사용)
        table_index: 업데이트할 테이블 인덱스 (0부터 시작, 기본값: 0)
        headers: 새 헤더 (JSON 배열, 예: '["이름", "나이", "이메일"]'. null이면 기존 유지)
        rows: 새 데이터 행 (JSON 2D 배열, 예: '[["홍길동", "30", "hong@email.com"]]')

    Returns:
        업데이트 결과
    """
    try:
        # URL에서 page_id 추출
        if not page_id and url:
            page_id = extract_page_id_from_url(url)
            if not page_id:
                return f"URL에서 페이지 ID를 추출할 수 없습니다: {url}"

        if not page_id:
            return "page_id 또는 url 중 하나는 필수입니다."

        # JSON 파싱
        new_headers = json.loads(headers) if headers else None
        new_rows = json.loads(rows)

        # 기존 페이지 조회
        page = _get_page_by_id(page_id)
        current_version = page.get("version", {}).get("number", 1)
        current_title = page.get("title", "")
        current_body = page.get("body", {}).get("storage", {}).get("value", "")

        # 테이블 업데이트
        updated_body = update_table_in_html(current_body, table_index, new_headers, new_rows)

        # 페이지 업데이트
        api_url = f"{API_V1_BASE}/content/{page_id}"
        payload = {
            "id": page_id,
            "type": "page",
            "title": current_title,
            "version": {"number": current_version + 1},
            "body": {
                "storage": {
                    "value": updated_body,
                    "representation": "storage"
                }
            }
        }

        response = requests.put(api_url, auth=_get_auth(), headers=_get_headers(), json=payload)
        response.raise_for_status()

        result = response.json()
        page_url = f"{CONFLUENCE_URL}/wiki{result.get('_links', {}).get('webui', '')}"
        new_version = result.get("version", {}).get("number", current_version + 1)

        return (
            f"## 테이블 업데이트 완료\n\n"
            f"- **페이지**: {current_title}\n"
            f"- **테이블 인덱스**: {table_index}\n"
            f"- **버전**: {current_version} → {new_version}\n"
            f"- **행 수**: {len(new_rows)}\n"
            f"- **URL**: {page_url}"
        )

    except json.JSONDecodeError as e:
        return f"JSON 파싱 오류: {str(e)}"
    except ValueError as e:
        return f"값 오류: {str(e)}"
    except requests.HTTPError as e:
        return f"API 오류: {e.response.status_code} - {e.response.text}"
    except Exception as e:
        return f"오류: {str(e)}"


@mcp.tool()
def add_table_row(
    page_id: Optional[str] = None,
    url: Optional[str] = None,
    table_index: int = 0,
    row_data: str = "[]"
) -> str:
    """페이지 내 테이블에 행을 추가합니다.

    Args:
        page_id: 페이지 ID (선택)
        url: 페이지 URL (선택, page_id가 없을 때 사용)
        table_index: 테이블 인덱스 (0부터 시작, 기본값: 0)
        row_data: 추가할 행 데이터 (JSON 배열, 예: '["홍길동", "30", "hong@email.com"]')

    Returns:
        추가 결과
    """
    try:
        # URL에서 page_id 추출
        if not page_id and url:
            page_id = extract_page_id_from_url(url)
            if not page_id:
                return f"URL에서 페이지 ID를 추출할 수 없습니다: {url}"

        if not page_id:
            return "page_id 또는 url 중 하나는 필수입니다."

        # JSON 파싱
        new_row = json.loads(row_data)

        # 기존 페이지 조회
        page = _get_page_by_id(page_id)
        current_version = page.get("version", {}).get("number", 1)
        current_title = page.get("title", "")
        current_body = page.get("body", {}).get("storage", {}).get("value", "")

        # 행 추가
        updated_body = add_row_to_table(current_body, table_index, new_row)

        # 페이지 업데이트
        api_url = f"{API_V1_BASE}/content/{page_id}"
        payload = {
            "id": page_id,
            "type": "page",
            "title": current_title,
            "version": {"number": current_version + 1},
            "body": {
                "storage": {
                    "value": updated_body,
                    "representation": "storage"
                }
            }
        }

        response = requests.put(api_url, auth=_get_auth(), headers=_get_headers(), json=payload)
        response.raise_for_status()

        result = response.json()
        page_url = f"{CONFLUENCE_URL}/wiki{result.get('_links', {}).get('webui', '')}"
        new_version = result.get("version", {}).get("number", current_version + 1)

        return (
            f"## 테이블에 행 추가 완료\n\n"
            f"- **페이지**: {current_title}\n"
            f"- **테이블 인덱스**: {table_index}\n"
            f"- **버전**: {current_version} → {new_version}\n"
            f"- **추가된 데이터**: {new_row}\n"
            f"- **URL**: {page_url}"
        )

    except json.JSONDecodeError as e:
        return f"JSON 파싱 오류: {str(e)}"
    except ValueError as e:
        return f"값 오류: {str(e)}"
    except requests.HTTPError as e:
        return f"API 오류: {e.response.status_code} - {e.response.text}"
    except Exception as e:
        return f"오류: {str(e)}"


@mcp.tool()
def delete_table_row(
    page_id: Optional[str] = None,
    url: Optional[str] = None,
    table_index: int = 0,
    row_index: int = 0
) -> str:
    """페이지 내 테이블에서 행을 삭제합니다.

    Args:
        page_id: 페이지 ID (선택)
        url: 페이지 URL (선택, page_id가 없을 때 사용)
        table_index: 테이블 인덱스 (0부터 시작, 기본값: 0)
        row_index: 삭제할 행 인덱스 (0부터 시작, 헤더 제외)

    Returns:
        삭제 결과
    """
    try:
        # URL에서 page_id 추출
        if not page_id and url:
            page_id = extract_page_id_from_url(url)
            if not page_id:
                return f"URL에서 페이지 ID를 추출할 수 없습니다: {url}"

        if not page_id:
            return "page_id 또는 url 중 하나는 필수입니다."

        # 기존 페이지 조회
        page = _get_page_by_id(page_id)
        current_version = page.get("version", {}).get("number", 1)
        current_title = page.get("title", "")
        current_body = page.get("body", {}).get("storage", {}).get("value", "")

        # 행 삭제
        updated_body = delete_row_from_table(current_body, table_index, row_index)

        # 페이지 업데이트
        api_url = f"{API_V1_BASE}/content/{page_id}"
        payload = {
            "id": page_id,
            "type": "page",
            "title": current_title,
            "version": {"number": current_version + 1},
            "body": {
                "storage": {
                    "value": updated_body,
                    "representation": "storage"
                }
            }
        }

        response = requests.put(api_url, auth=_get_auth(), headers=_get_headers(), json=payload)
        response.raise_for_status()

        result = response.json()
        page_url = f"{CONFLUENCE_URL}/wiki{result.get('_links', {}).get('webui', '')}"
        new_version = result.get("version", {}).get("number", current_version + 1)

        return (
            f"## 테이블에서 행 삭제 완료\n\n"
            f"- **페이지**: {current_title}\n"
            f"- **테이블 인덱스**: {table_index}\n"
            f"- **삭제된 행 인덱스**: {row_index}\n"
            f"- **버전**: {current_version} → {new_version}\n"
            f"- **URL**: {page_url}"
        )

    except ValueError as e:
        return f"값 오류: {str(e)}"
    except requests.HTTPError as e:
        return f"API 오류: {e.response.status_code} - {e.response.text}"
    except Exception as e:
        return f"오류: {str(e)}"


@mcp.tool()
def update_table_cell(
    page_id: Optional[str] = None,
    url: Optional[str] = None,
    table_index: int = 0,
    row_index: int = 0,
    col_index: int = 0,
    new_value: str = ""
) -> str:
    """페이지 내 테이블의 특정 셀 값을 수정합니다.

    Args:
        page_id: 페이지 ID (선택)
        url: 페이지 URL (선택, page_id가 없을 때 사용)
        table_index: 테이블 인덱스 (0부터 시작, 기본값: 0)
        row_index: 행 인덱스 (0부터 시작, 헤더 제외)
        col_index: 열 인덱스 (0부터 시작)
        new_value: 새 값

    Returns:
        수정 결과
    """
    try:
        # URL에서 page_id 추출
        if not page_id and url:
            page_id = extract_page_id_from_url(url)
            if not page_id:
                return f"URL에서 페이지 ID를 추출할 수 없습니다: {url}"

        if not page_id:
            return "page_id 또는 url 중 하나는 필수입니다."

        # 기존 페이지 조회
        page = _get_page_by_id(page_id)
        current_version = page.get("version", {}).get("number", 1)
        current_title = page.get("title", "")
        current_body = page.get("body", {}).get("storage", {}).get("value", "")

        # 셀 업데이트
        updated_body = update_cell_in_table(current_body, table_index, row_index, col_index, new_value)

        # 페이지 업데이트
        api_url = f"{API_V1_BASE}/content/{page_id}"
        payload = {
            "id": page_id,
            "type": "page",
            "title": current_title,
            "version": {"number": current_version + 1},
            "body": {
                "storage": {
                    "value": updated_body,
                    "representation": "storage"
                }
            }
        }

        response = requests.put(api_url, auth=_get_auth(), headers=_get_headers(), json=payload)
        response.raise_for_status()

        result = response.json()
        page_url = f"{CONFLUENCE_URL}/wiki{result.get('_links', {}).get('webui', '')}"
        new_version = result.get("version", {}).get("number", current_version + 1)

        return (
            f"## 셀 값 수정 완료\n\n"
            f"- **페이지**: {current_title}\n"
            f"- **테이블 인덱스**: {table_index}\n"
            f"- **위치**: ({row_index}, {col_index})\n"
            f"- **새 값**: {new_value}\n"
            f"- **버전**: {current_version} → {new_version}\n"
            f"- **URL**: {page_url}"
        )

    except ValueError as e:
        return f"값 오류: {str(e)}"
    except requests.HTTPError as e:
        return f"API 오류: {e.response.status_code} - {e.response.text}"
    except Exception as e:
        return f"오류: {str(e)}"


@mcp.tool()
def create_page_with_table(
    space_key: str,
    title: str,
    table_headers: str,
    table_rows: str = "[]",
    description: str = "",
    parent_id: Optional[str] = None
) -> str:
    """테이블이 포함된 새 Confluence 페이지를 생성합니다.

    Args:
        space_key: 공간 Key (예: DEV, TEAM)
        title: 페이지 제목
        table_headers: 테이블 헤더 (JSON 배열, 예: '["이름", "나이", "이메일"]')
        table_rows: 테이블 데이터 (JSON 2D 배열, 예: '[["홍길동", "30", "hong@email.com"]]')
        description: 테이블 위에 추가할 설명 (Markdown, 선택)
        parent_id: 부모 페이지 ID (선택)

    Returns:
        생성된 페이지 정보
    """
    try:
        # JSON 파싱
        headers = json.loads(table_headers)
        rows = json.loads(table_rows)

        # HTML 테이블 생성
        table_html = list_to_html_table(headers, rows)

        # 설명 추가
        if description:
            desc_html = markdown_to_storage(description)
            body_content = f"{desc_html}<p></p>{table_html}"
        else:
            body_content = table_html

        # 페이지 생성
        url = f"{API_V1_BASE}/content"
        payload = {
            "type": "page",
            "title": title,
            "space": {"key": space_key},
            "body": {
                "storage": {
                    "value": body_content,
                    "representation": "storage"
                }
            }
        }

        if parent_id:
            payload["ancestors"] = [{"id": parent_id}]

        response = requests.post(url, auth=_get_auth(), headers=_get_headers(), json=payload)
        response.raise_for_status()

        result = response.json()
        page_id = result.get("id")
        page_url = f"{CONFLUENCE_URL}/wiki{result.get('_links', {}).get('webui', '')}"

        return (
            f"## 테이블 페이지 생성 완료\n\n"
            f"- **제목**: {title}\n"
            f"- **페이지 ID**: `{page_id}`\n"
            f"- **공간**: {space_key}\n"
            f"- **테이블**: {len(rows)}행 x {len(headers)}열\n"
            f"- **URL**: {page_url}"
        )

    except json.JSONDecodeError as e:
        return f"JSON 파싱 오류: {str(e)}"
    except requests.HTTPError as e:
        error_msg = e.response.text
        if "title" in error_msg.lower() and "already exists" in error_msg.lower():
            return f"동일한 제목의 페이지가 이미 존재합니다: {title}"
        return f"API 오류: {e.response.status_code} - {error_msg}"
    except Exception as e:
        return f"오류: {str(e)}"


# ============================================================
# Confluence Database MCP Tools (메타데이터 수준만 지원)
# ============================================================

@mcp.tool()
def list_databases(
    space_id: Optional[str] = None,
    limit: int = 25
) -> str:
    """Confluence 데이터베이스 목록을 조회합니다.

    참고: Confluence REST API는 현재 데이터베이스 메타데이터만 지원합니다.
    행(row) 수준의 CRUD는 API에서 지원되지 않습니다.

    Args:
        space_id: 특정 스페이스로 제한 (선택)
        limit: 조회할 데이터베이스 수 (기본값: 25, 최대: 100)

    Returns:
        데이터베이스 목록
    """
    try:
        limit = min(limit, 100)

        # 데이터베이스 검색 (CQL 사용)
        cql = 'type=database'
        if space_id:
            cql += f' AND space.id={space_id}'

        url = f"{API_V1_BASE}/content/search"
        params = {"cql": cql, "limit": limit}

        response = requests.get(url, auth=_get_auth(), headers=_get_headers(), params=params)
        response.raise_for_status()

        data = response.json()
        results = data.get("results", [])

        if not results:
            return "데이터베이스가 없습니다."

        output = []
        for db in results:
            db_id = db.get("id")
            db_title = db.get("title", "제목 없음")
            space_name = db.get("space", {}).get("name", "N/A")
            db_url = f"{CONFLUENCE_URL}/wiki{db.get('_links', {}).get('webui', '')}"

            output.append(
                f"- **{db_title}**\n"
                f"  ID: `{db_id}` | 공간: {space_name}\n"
                f"  URL: {db_url}"
            )

        note = (
            "\n\n> ⚠️ **참고**: Confluence REST API는 현재 데이터베이스의 행(row) 조회/수정을 지원하지 않습니다.\n"
            "> 데이터 핸들링이 필요한 경우 **페이지 내 테이블** 기능(`get_page_tables`, `update_page_table` 등)을 사용하세요."
        )

        return f"## Confluence 데이터베이스 목록 ({len(results)}개)\n\n" + "\n\n".join(output) + note

    except requests.RequestException as e:
        return f"API 오류: {str(e)}"


@mcp.tool()
def get_database(
    database_id: str
) -> str:
    """Confluence 데이터베이스 정보를 조회합니다.

    참고: Confluence REST API는 데이터베이스 메타데이터만 제공합니다.
    실제 행 데이터는 API로 조회할 수 없습니다.

    Args:
        database_id: 데이터베이스 ID

    Returns:
        데이터베이스 정보
    """
    try:
        url = f"{API_V2_BASE}/databases/{database_id}"

        response = requests.get(url, auth=_get_auth(), headers=_get_headers())
        response.raise_for_status()

        db = response.json()

        db_title = db.get("title", "제목 없음")
        space_id = db.get("spaceId", "N/A")
        created_at = db.get("createdAt", "N/A")
        author_id = db.get("authorId", "N/A")

        note = (
            "\n> ⚠️ **API 제한**: Confluence REST API는 데이터베이스 행(row) 조회를 지원하지 않습니다.\n"
            "> 웹 UI에서 직접 확인하거나, 데이터 핸들링이 필요한 경우 **페이지 내 테이블**을 사용하세요."
        )

        return (
            f"## 데이터베이스 정보\n\n"
            f"- **제목**: {db_title}\n"
            f"- **ID**: `{database_id}`\n"
            f"- **Space ID**: {space_id}\n"
            f"- **생성일**: {created_at}\n"
            f"- **작성자 ID**: {author_id}\n"
            f"{note}"
        )

    except requests.HTTPError as e:
        if e.response.status_code == 404:
            return f"데이터베이스를 찾을 수 없습니다: {database_id}"
        return f"API 오류: {e.response.status_code} - {e.response.text}"
    except requests.RequestException as e:
        return f"요청 오류: {str(e)}"


@mcp.tool()
def create_database(
    space_id: str,
    title: str,
    parent_id: Optional[str] = None
) -> str:
    """새 Confluence 데이터베이스를 생성합니다.

    참고: 데이터베이스는 생성만 가능하며, 행 추가/수정은 웹 UI에서만 가능합니다.
    데이터 핸들링이 필요한 경우 페이지 내 테이블 사용을 권장합니다.

    Args:
        space_id: 공간 ID
        title: 데이터베이스 제목
        parent_id: 부모 페이지 ID (선택)

    Returns:
        생성된 데이터베이스 정보
    """
    try:
        url = f"{API_V2_BASE}/databases"

        payload = {
            "spaceId": space_id,
            "title": title
        }

        if parent_id:
            payload["parentId"] = parent_id

        response = requests.post(url, auth=_get_auth(), headers=_get_headers(), json=payload)
        response.raise_for_status()

        result = response.json()
        db_id = result.get("id")

        note = (
            "\n> ⚠️ **다음 단계**: 데이터베이스가 생성되었습니다.\n"
            "> 열(컬럼)과 행 추가는 Confluence 웹 UI에서 진행해주세요.\n"
            "> API를 통한 행 수준 작업은 현재 지원되지 않습니다."
        )

        return (
            f"## 데이터베이스 생성 완료\n\n"
            f"- **제목**: {title}\n"
            f"- **ID**: `{db_id}`\n"
            f"- **Space ID**: {space_id}\n"
            f"{note}"
        )

    except requests.HTTPError as e:
        error_msg = e.response.text
        if "title" in error_msg.lower() and "already exists" in error_msg.lower():
            return f"동일한 제목의 데이터베이스가 이미 존재합니다: {title}"
        return f"API 오류: {e.response.status_code} - {error_msg}"
    except requests.RequestException as e:
        return f"요청 오류: {str(e)}"


@mcp.tool()
def delete_database(
    database_id: str
) -> str:
    """Confluence 데이터베이스를 삭제합니다.

    Args:
        database_id: 삭제할 데이터베이스 ID

    Returns:
        삭제 결과
    """
    try:
        url = f"{API_V2_BASE}/databases/{database_id}"

        response = requests.delete(url, auth=_get_auth(), headers=_get_headers())
        response.raise_for_status()

        return (
            f"## 데이터베이스 삭제 완료\n\n"
            f"- **삭제된 ID**: `{database_id}`"
        )

    except requests.HTTPError as e:
        if e.response.status_code == 404:
            return f"데이터베이스를 찾을 수 없습니다: {database_id}"
        return f"API 오류: {e.response.status_code} - {e.response.text}"
    except requests.RequestException as e:
        return f"요청 오류: {str(e)}"


def _normalize_html(html_content: str) -> str:
    """HTML 내용을 정규화하여 비교 가능하게 만듦

    - 연속 공백을 단일 공백으로
    - 줄바꿈 정규화
    - 태그 사이 공백 제거
    """
    if not html_content:
        return ""
    # 줄바꿈을 공백으로
    normalized = re.sub(r'\n+', ' ', html_content)
    # 연속 공백을 단일 공백으로
    normalized = re.sub(r'\s+', ' ', normalized)
    # 태그 앞뒤 공백 제거
    normalized = re.sub(r'>\s+<', '><', normalized)
    # 앞뒤 공백 제거
    normalized = normalized.strip()
    return normalized


def _generate_diff(old_content: str, new_content: str, context_lines: int = 3) -> str:
    """두 내용의 diff를 생성 (Markdown 기준)"""
    old_lines = old_content.splitlines(keepends=True)
    new_lines = new_content.splitlines(keepends=True)

    diff = difflib.unified_diff(
        old_lines,
        new_lines,
        fromfile='현재 내용',
        tofile='새 내용',
        lineterm='',
        n=context_lines
    )

    return ''.join(diff)


@mcp.tool()
def smart_update_page(
    page_id: Optional[str] = None,
    url: Optional[str] = None,
    content: Optional[str] = None,
    title: Optional[str] = None,
    is_markdown: bool = True,
    dry_run: bool = False
) -> str:
    """변경 사항이 있을 때만 페이지를 업데이트합니다.

    기존 내용과 새 내용을 비교하여 실제 변경이 있는 경우에만 API를 호출합니다.
    dry_run=True로 설정하면 실제 업데이트 없이 변경 사항만 미리 확인할 수 있습니다.

    Args:
        page_id: 페이지 ID (선택)
        url: 페이지 URL (선택, page_id가 없을 때 사용)
        content: 새 내용 (Markdown 또는 HTML)
        title: 새 제목 (선택, 없으면 기존 제목 유지)
        is_markdown: content가 Markdown인지 여부 (기본값: True)
        dry_run: True면 변경 사항만 출력하고 실제 업데이트 안 함 (기본값: False)

    Returns:
        업데이트 결과 또는 변경 사항 diff
    """
    try:
        # URL에서 page_id 추출
        if not page_id and url:
            page_id = extract_page_id_from_url(url)
            if not page_id:
                return f"URL에서 페이지 ID를 추출할 수 없습니다: {url}"

        if not page_id:
            return "page_id 또는 url 중 하나는 필수입니다."

        if not content:
            return "content는 필수입니다."

        # 기존 페이지 정보 조회
        current_page = _get_page_by_id(page_id)
        current_version = current_page.get("version", {}).get("number", 1)
        current_title = current_page.get("title", "")
        current_body = current_page.get("body", {}).get("storage", {}).get("value", "")

        # 새 내용 변환
        if is_markdown:
            new_body = markdown_to_storage(content)
        else:
            new_body = content

        new_title = title if title else current_title

        # 내용 비교 (정규화 후)
        current_normalized = _normalize_html(current_body)
        new_normalized = _normalize_html(new_body)

        title_changed = (title is not None) and (new_title != current_title)
        content_changed = current_normalized != new_normalized

        if not title_changed and not content_changed:
            return (
                f"## 변경 사항 없음\n\n"
                f"- **페이지**: {current_title}\n"
                f"- **페이지 ID**: `{page_id}`\n"
                f"- **현재 버전**: {current_version}\n\n"
                f"내용이 동일하여 업데이트를 건너뜁니다."
            )

        # 변경 사항 요약
        changes = []
        if title_changed:
            changes.append(f"- 제목: `{current_title}` → `{new_title}`")
        if content_changed:
            # Markdown 기준으로 diff 생성
            current_md = storage_to_markdown(current_body)
            new_md = content if is_markdown else storage_to_markdown(new_body)
            diff_text = _generate_diff(current_md, new_md)
            changes.append(f"- 내용 변경됨")

        # dry_run이면 diff만 출력
        if dry_run:
            result = (
                f"## 변경 사항 미리보기 (Dry Run)\n\n"
                f"- **페이지**: {current_title}\n"
                f"- **페이지 ID**: `{page_id}`\n"
                f"- **현재 버전**: {current_version}\n\n"
                f"### 변경 내용\n"
                + "\n".join(changes)
            )

            if content_changed:
                result += f"\n\n### Diff\n```diff\n{diff_text}\n```"

            result += "\n\n> ℹ️ `dry_run=False`로 설정하면 실제 업데이트가 수행됩니다."
            return result

        # 실제 업데이트 수행
        api_url = f"{API_V1_BASE}/content/{page_id}"

        payload = {
            "id": page_id,
            "type": "page",
            "title": new_title,
            "version": {"number": current_version + 1},
            "body": {
                "storage": {
                    "value": new_body,
                    "representation": "storage"
                }
            }
        }

        response = requests.put(
            api_url,
            auth=_get_auth(),
            headers=_get_headers(),
            json=payload
        )
        response.raise_for_status()

        result_data = response.json()
        page_url = f"{CONFLUENCE_URL}/wiki{result_data.get('_links', {}).get('webui', '')}"
        new_version = result_data.get("version", {}).get("number", current_version + 1)

        result = (
            f"## 페이지 업데이트 완료\n\n"
            f"- **제목**: {new_title}\n"
            f"- **페이지 ID**: `{page_id}`\n"
            f"- **버전**: {current_version} → {new_version}\n"
            f"- **URL**: {page_url}\n\n"
            f"### 변경 내용\n"
            + "\n".join(changes)
        )

        return result

    except requests.HTTPError as e:
        return f"API 오류: {e.response.status_code} - {e.response.text}"
    except requests.RequestException as e:
        return f"요청 오류: {str(e)}"


def _parse_markdown_sections(md_content: str) -> List[Dict[str, Any]]:
    """Markdown 내용을 섹션별로 파싱

    Returns:
        [
            {"level": 2, "heading": "## 1. 개요", "content": "내용...", "start": 0, "end": 50},
            {"level": 2, "heading": "## 2. 구현", "content": "내용...", "start": 50, "end": 100},
            ...
        ]
    """
    sections = []
    # 헤딩 패턴: ^#{1,6}\s+.+$
    heading_pattern = re.compile(r'^(#{1,6})\s+(.+)$', re.MULTILINE)

    matches = list(heading_pattern.finditer(md_content))

    if not matches:
        # 헤딩이 없으면 전체를 하나의 섹션으로
        return [{"level": 0, "heading": "", "title": "", "content": md_content, "start": 0, "end": len(md_content)}]

    # 첫 헤딩 전 내용이 있으면 추가
    if matches[0].start() > 0:
        sections.append({
            "level": 0,
            "heading": "",
            "title": "",
            "content": md_content[:matches[0].start()],
            "start": 0,
            "end": matches[0].start()
        })

    for i, match in enumerate(matches):
        level = len(match.group(1))  # # 개수
        heading = match.group(0)  # 전체 헤딩 라인
        title = match.group(2).strip()  # 헤딩 텍스트만

        # 다음 섹션 시작점 (같은 레벨 이상의 다음 헤딩)
        content_start = match.end()
        content_end = len(md_content)

        for next_match in matches[i + 1:]:
            next_level = len(next_match.group(1))
            if next_level <= level:
                content_end = next_match.start()
                break

        content = md_content[content_start:content_end].strip()

        sections.append({
            "level": level,
            "heading": heading,
            "title": title,
            "content": content,
            "start": match.start(),
            "end": content_end
        })

    return sections


def _find_section(sections: List[Dict], heading_text: str) -> Optional[Dict]:
    """섹션 목록에서 헤딩 텍스트로 섹션 찾기

    heading_text는 다음 형식 모두 지원:
    - "## 1. 개요" (전체 헤딩)
    - "1. 개요" (제목만)
    - "개요" (부분 매칭)
    """
    heading_text = heading_text.strip()

    # 1. 전체 헤딩 정확히 매칭
    for section in sections:
        if section["heading"] == heading_text:
            return section

    # 2. 제목만 정확히 매칭
    for section in sections:
        if section["title"] == heading_text:
            return section

    # 3. 제목에 포함되는지 (부분 매칭)
    for section in sections:
        if heading_text in section["title"]:
            return section

    return None


def _replace_section_content(md_content: str, sections: List[Dict], target_section: Dict, new_content: str) -> str:
    """특정 섹션의 내용을 교체"""
    # 헤딩은 유지하고 내용만 교체
    heading_end = md_content.find('\n', target_section["start"])
    if heading_end == -1:
        heading_end = target_section["start"] + len(target_section["heading"])

    # 새 섹션 내용 구성
    new_section = md_content[target_section["start"]:heading_end + 1] + "\n" + new_content.strip() + "\n\n"

    # 원본에서 섹션 교체
    result = md_content[:target_section["start"]] + new_section + md_content[target_section["end"]:]

    return result


@mcp.tool()
def update_page_section(
    page_id: Optional[str] = None,
    url: Optional[str] = None,
    section_heading: str = "",
    new_content: str = "",
    dry_run: bool = False
) -> str:
    """페이지의 특정 섹션만 업데이트합니다.

    헤딩(##, ###)을 기준으로 섹션을 찾아 해당 섹션의 내용만 교체합니다.
    다른 섹션은 그대로 유지됩니다.

    Args:
        page_id: 페이지 ID (선택)
        url: 페이지 URL (선택, page_id가 없을 때 사용)
        section_heading: 업데이트할 섹션의 헤딩 (예: "## 3. 구현 현황", "구현 현황", "3. 구현")
        new_content: 해당 섹션의 새 내용 (Markdown, 헤딩 제외)
        dry_run: True면 변경 사항만 출력하고 실제 업데이트 안 함 (기본값: False)

    Returns:
        업데이트 결과 또는 변경 사항 diff
    """
    try:
        # URL에서 page_id 추출
        if not page_id and url:
            page_id = extract_page_id_from_url(url)
            if not page_id:
                return f"URL에서 페이지 ID를 추출할 수 없습니다: {url}"

        if not page_id:
            return "page_id 또는 url 중 하나는 필수입니다."

        if not section_heading:
            return "section_heading은 필수입니다."

        if not new_content:
            return "new_content는 필수입니다."

        # 기존 페이지 정보 조회
        current_page = _get_page_by_id(page_id)
        current_version = current_page.get("version", {}).get("number", 1)
        current_title = current_page.get("title", "")
        current_body = current_page.get("body", {}).get("storage", {}).get("value", "")

        # Markdown으로 변환
        current_md = storage_to_markdown(current_body)

        # 섹션 파싱
        sections = _parse_markdown_sections(current_md)

        # 타겟 섹션 찾기
        target_section = _find_section(sections, section_heading)

        if not target_section:
            available_sections = [s["heading"] for s in sections if s["heading"]]
            return (
                f"## 섹션을 찾을 수 없음\n\n"
                f"- **검색한 헤딩**: `{section_heading}`\n\n"
                f"### 사용 가능한 섹션\n"
                + "\n".join([f"- `{h}`" for h in available_sections])
            )

        # 섹션 내용 교체
        new_md = _replace_section_content(current_md, sections, target_section, new_content)

        # 변경 확인
        if current_md.strip() == new_md.strip():
            return (
                f"## 변경 사항 없음\n\n"
                f"- **페이지**: {current_title}\n"
                f"- **섹션**: `{target_section['heading']}`\n\n"
                f"내용이 동일하여 업데이트를 건너뜁니다."
            )

        # Diff 생성
        diff_text = _generate_diff(current_md, new_md)

        # dry_run이면 diff만 출력
        if dry_run:
            return (
                f"## 섹션 업데이트 미리보기 (Dry Run)\n\n"
                f"- **페이지**: {current_title}\n"
                f"- **페이지 ID**: `{page_id}`\n"
                f"- **섹션**: `{target_section['heading']}`\n"
                f"- **현재 버전**: {current_version}\n\n"
                f"### Diff\n```diff\n{diff_text}\n```\n\n"
                f"> ℹ️ `dry_run=False`로 설정하면 실제 업데이트가 수행됩니다."
            )

        # Confluence 형식으로 변환
        new_body = markdown_to_storage(new_md)

        # 실제 업데이트 수행
        api_url = f"{API_V1_BASE}/content/{page_id}"

        payload = {
            "id": page_id,
            "type": "page",
            "title": current_title,
            "version": {"number": current_version + 1},
            "body": {
                "storage": {
                    "value": new_body,
                    "representation": "storage"
                }
            }
        }

        response = requests.put(
            api_url,
            auth=_get_auth(),
            headers=_get_headers(),
            json=payload
        )
        response.raise_for_status()

        result_data = response.json()
        page_url = f"{CONFLUENCE_URL}/wiki{result_data.get('_links', {}).get('webui', '')}"
        new_version = result_data.get("version", {}).get("number", current_version + 1)

        return (
            f"## 섹션 업데이트 완료\n\n"
            f"- **페이지**: {current_title}\n"
            f"- **페이지 ID**: `{page_id}`\n"
            f"- **업데이트된 섹션**: `{target_section['heading']}`\n"
            f"- **버전**: {current_version} → {new_version}\n"
            f"- **URL**: {page_url}\n\n"
            f"### 변경된 내용\n```diff\n{diff_text}\n```"
        )

    except requests.HTTPError as e:
        return f"API 오류: {e.response.status_code} - {e.response.text}"
    except requests.RequestException as e:
        return f"요청 오류: {str(e)}"


@mcp.tool()
def list_page_sections(
    page_id: Optional[str] = None,
    url: Optional[str] = None
) -> str:
    """페이지의 섹션(헤딩) 목록을 조회합니다.

    update_page_section에서 사용할 section_heading 값을 확인할 때 유용합니다.

    Args:
        page_id: 페이지 ID (선택)
        url: 페이지 URL (선택, page_id가 없을 때 사용)

    Returns:
        섹션 목록 (헤딩 레벨, 제목)
    """
    try:
        # URL에서 page_id 추출
        if not page_id and url:
            page_id = extract_page_id_from_url(url)
            if not page_id:
                return f"URL에서 페이지 ID를 추출할 수 없습니다: {url}"

        if not page_id:
            return "page_id 또는 url 중 하나는 필수입니다."

        # 페이지 조회
        current_page = _get_page_by_id(page_id)
        current_title = current_page.get("title", "")
        current_body = current_page.get("body", {}).get("storage", {}).get("value", "")

        # Markdown으로 변환
        current_md = storage_to_markdown(current_body)

        # 섹션 파싱
        sections = _parse_markdown_sections(current_md)

        # 결과 포맷
        result = (
            f"## 페이지 섹션 목록\n\n"
            f"- **페이지**: {current_title}\n"
            f"- **페이지 ID**: `{page_id}`\n\n"
            f"### 섹션\n\n"
            f"| 레벨 | 헤딩 | 내용 미리보기 |\n"
            f"|------|------|---------------|\n"
        )

        for section in sections:
            if section["heading"]:  # 헤딩이 있는 섹션만
                level = section["level"]
                heading = section["heading"].replace("|", "\\|")
                preview = section["content"][:50].replace("\n", " ").replace("|", "\\|")
                if len(section["content"]) > 50:
                    preview += "..."
                result += f"| h{level} | `{heading}` | {preview} |\n"

        result += (
            f"\n### 사용 예시\n"
            f"```\n"
            f"update_page_section(\n"
            f"    page_id=\"{page_id}\",\n"
            f"    section_heading=\"## 섹션명\",\n"
            f"    new_content=\"새로운 내용...\"\n"
            f")\n"
            f"```"
        )

        return result

    except requests.HTTPError as e:
        return f"API 오류: {e.response.status_code} - {e.response.text}"
    except requests.RequestException as e:
        return f"요청 오류: {str(e)}"


def upload_attachment_to_page(page_id: str, file_path: str, comment: str = "") -> Tuple[bool, str, Optional[str]]:
    """Confluence 페이지에 첨부파일 업로드 (내부 함수)

    Args:
        page_id: 페이지 ID
        file_path: 업로드할 파일 경로
        comment: 첨부파일 코멘트 (선택)

    Returns:
        (성공 여부, 메시지, 첨부파일 ID 또는 None)
    """
    try:
        file_path = Path(file_path)
        if not file_path.exists():
            return False, f"파일을 찾을 수 없음: {file_path}", None

        filename = file_path.name

        # 첨부파일 업로드 API
        url = f"{API_V1_BASE}/content/{page_id}/child/attachment"

        # 멀티파트 폼 데이터 준비
        headers = {
            "X-Atlassian-Token": "nocheck",  # CSRF 보호 비활성화 필수
        }

        with open(file_path, 'rb') as f:
            files = {
                'file': (filename, f, 'application/octet-stream')
            }
            data = {}
            if comment:
                data['comment'] = comment

            response = requests.post(
                url,
                auth=_get_auth(),
                headers=headers,
                files=files,
                data=data
            )

        if response.status_code == 200:
            # 기존 파일 업데이트됨
            result = response.json().get("results", [{}])[0]
            att_id = result.get("id")
            return True, f"첨부파일 업데이트됨: {filename}", att_id
        elif response.status_code == 201:
            # 새 파일 업로드됨
            result = response.json().get("results", [{}])[0]
            att_id = result.get("id")
            return True, f"첨부파일 업로드됨: {filename}", att_id
        else:
            response.raise_for_status()
            return False, f"업로드 실패: {response.status_code}", None

    except requests.RequestException as e:
        error_msg = str(e)
        if hasattr(e, 'response') and e.response is not None:
            error_msg = f"{e.response.status_code} - {e.response.text}"
        return False, f"업로드 오류: {error_msg}", None
    except Exception as e:
        return False, f"오류: {str(e)}", None


@mcp.tool()
def upload_attachment(
    page_id: Optional[str] = None,
    url: Optional[str] = None,
    file_path: str = "",
    comment: str = ""
) -> str:
    """Confluence 페이지에 첨부파일(이미지 등)을 업로드합니다.

    Args:
        page_id: 페이지 ID (선택)
        url: 페이지 URL (선택, page_id가 없을 때 사용)
        file_path: 업로드할 파일의 로컬 경로
        comment: 첨부파일 코멘트 (선택)

    Returns:
        업로드 결과
    """
    try:
        # page_id 또는 url로 페이지 ID 확보
        if not page_id and url:
            page_id = extract_page_id_from_url(url)
            if not page_id:
                return f"URL에서 페이지 ID를 추출할 수 없습니다: {url}"

        if not page_id:
            return "page_id 또는 url 중 하나를 제공해야 합니다."

        if not file_path:
            return "file_path를 제공해야 합니다."

        success, message, att_id = upload_attachment_to_page(page_id, file_path, comment)

        if success:
            # 업로드된 파일의 참조 방법 안내
            filename = Path(file_path).name
            result = (
                f"## 업로드 완료\n\n"
                f"- **파일**: {filename}\n"
                f"- **페이지 ID**: {page_id}\n"
                f"- **첨부파일 ID**: {att_id}\n\n"
                f"### 페이지에서 이미지 참조 방법\n\n"
                f"Confluence Storage Format:\n"
                f"```html\n"
                f"<ac:image><ri:attachment ri:filename=\"{filename}\" /></ac:image>\n"
                f"```\n\n"
                f"Markdown (update_page 사용 시 자동 변환):\n"
                f"```markdown\n"
                f"![{filename}]({filename})\n"
                f"```"
            )
            return result
        else:
            return f"업로드 실패: {message}"

    except Exception as e:
        return f"오류: {str(e)}"


@mcp.tool()
def upload_attachments(
    page_id: Optional[str] = None,
    url: Optional[str] = None,
    file_paths: str = "",
    comment: str = ""
) -> str:
    """Confluence 페이지에 여러 첨부파일을 한 번에 업로드합니다.

    Args:
        page_id: 페이지 ID (선택)
        url: 페이지 URL (선택, page_id가 없을 때 사용)
        file_paths: 업로드할 파일 경로들 (쉼표로 구분, 예: "file1.png,file2.png")
        comment: 첨부파일 코멘트 (선택)

    Returns:
        업로드 결과
    """
    try:
        # page_id 또는 url로 페이지 ID 확보
        if not page_id and url:
            page_id = extract_page_id_from_url(url)
            if not page_id:
                return f"URL에서 페이지 ID를 추출할 수 없습니다: {url}"

        if not page_id:
            return "page_id 또는 url 중 하나를 제공해야 합니다."

        if not file_paths:
            return "file_paths를 제공해야 합니다."

        # 파일 경로 파싱
        paths = [p.strip() for p in file_paths.split(",") if p.strip()]

        if not paths:
            return "유효한 파일 경로가 없습니다."

        results = []
        success_count = 0
        fail_count = 0

        for path in paths:
            success, message, att_id = upload_attachment_to_page(page_id, path, comment)
            filename = Path(path).name

            if success:
                success_count += 1
                results.append(f"✅ {filename}")
            else:
                fail_count += 1
                results.append(f"❌ {filename}: {message}")

        result = (
            f"## 업로드 결과\n\n"
            f"- **페이지 ID**: {page_id}\n"
            f"- **성공**: {success_count}개\n"
            f"- **실패**: {fail_count}개\n\n"
            f"### 상세\n\n"
        )
        result += "\n".join(results)

        if success_count > 0:
            result += (
                f"\n\n### 이미지 참조 예시\n\n"
                f"```markdown\n"
                f"![파일명](파일명.png)\n"
                f"```"
            )

        return result

    except Exception as e:
        return f"오류: {str(e)}"


if __name__ == "__main__":
    mcp.run()
