import os
import requests
import re
from datetime import datetime, timedelta
from dotenv import load_dotenv
from fastmcp import FastMCP
from typing import Dict, Any, Optional, List

JIRA_URL = os.getenv("JIRA_URL")
JIRA_USERNAME = os.getenv("JIRA_USERNAME")
JIRA_PASSWORD = os.getenv("JIRA_PASSWORD")

# 환경 변수 확인
if not all([JIRA_URL, JIRA_USERNAME, JIRA_PASSWORD]):
    raise ValueError(f"'필수 환경 변수(JIRA_URL, JIRA_USERNAME, JIRA_PASSWORD)를 찾을 수 없습니다.")

# Basic Auth 설정
AUTH = (JIRA_USERNAME, JIRA_PASSWORD)

# LG전자 사내 Work Description 양식 상수
WORK_TYPES = [
    "휴가", "교육", "출장", "이슈", "지원", "개발", "운영",
    "미래:TRM연계", "미래:개발자발의", "미래:역량개발"
]

BIZ_DOMAINS = ["TV", "ID", "IT", "Audio", "Platform Biz", "Common", "ES", "VS", "HS", "Others"]

# 프로그램/특화모델명 유효한 값들
VALID_PROGRAMS = [
    # 다른 사업부 (연도 포함)
    "ID_2023", "ID_2024", "ID_2025", "ID_2026", "ID_2027",
    "IT_2023", "IT_2024", "IT_2025", "IT_2026", "IT_2027",
    "Audio_2023", "Audio_2024", "Audio_2025", "Audio_2026", "Audio_2027",
    # 공통
    "Common", "ServerApp",
    # webOS TV 버전들
    "webOS TV 27", "webOS TV 26", "webOS TV 25", "webOS TV 24", "webOS TV 23",
    "webOS TV 22", "webOS TV 6.0", "webOS TV 5.0", "webOS TV 4.5", "webOS TV 4.0",
    "webOS TV 3.5", "webOS TV 3.0", "webOS TV 2.0", "webOS TV 1.0",
    # 특화 모델들
    "오디오S7", "무선M6", "무선W6", "PetCareZone", "Layer", "StanByMe", "StanByMe2",
    "StanByMeGo", "StanbyMEII", "StanbyME2_4K", "무선AV", "무선M4", "아뜰리에",
    "MeMor", "PINE22", "PINE23", "PINE24", "PINE25", "PUMA", "벤더블", "스타워즈",
    "BASKIN", "CAT2", "SPK", "T-Native", "SeniorTV", "WEE3.0", "WEE", "WEE1.0",
    "WEE2.0", "WEE2.0S", "WEE2.0SS", "WEE2.0M", "WEE2.5M"
]

REQUIRED_PROGRAM_MODEL_TYPES = ["이슈", "지원", "개발"]
REQUIRED_BIZ_DOMAIN_TYPES = ["이슈", "지원", "개발"]

# Personal Work Log에 작성되어야 하는 업무유형
PERSONAL_WORK_TYPES = ["휴가", "교육"]

# Worklog 가이드를 위한 업무유형별 키워드 사전
WORK_TYPE_KEYWORDS = {
    "휴가": ["휴가", "연차", "반차", "반일", "유급휴가", "개인휴가", "sprint"],
    "교육": ["교육", "학습", "연수", "세미나", "강의", "워크샵", "컨퍼런스", "온라인교육", "오프라인교육", "사내교육", "사외교육", "수강"],
    "출장": ["출장", "출장업무", "현장방문", "외근", "현장", "방문"],
    "이슈": ["이슈", "문제해결", "버그분석", "인증", "필드이슈", "DIT", "Q", "문제", "오류", "장애", "분석", "TDD", "DQA", "외부부서", "검토", "개선"],
    "지원": ["지원", "sanity", "테스트", "평택", "구미", "해생지", "operation", "운영지원", "기술지원", "SW", "PL", "요청"],
    "개발": ["코드", "개발", "구현", "프로그래밍", "버그수정", "기능개발", "소스코드", "알고리즘", "API", "함수", "모듈", "팀장회의", "DDM", "점검회의", "보드간사", "non-initiative", "사업부", "연구소"],
    "운영": ["회의", "이사", "월례조회", "간사", "청소", "JB", "미팅", "보고", "점검", "관리", "비개발", "간사활동"],
    "미래:TRM연계": ["TRM", "로드맵", "기술로드맵", "전략", "PO", "리뷰", "미래준비", "technical", "roadmap"],
    "미래:개발자발의": ["PoC", "성능개선", "보안강화", "아이디어", "신기술", "프레임워크", "최적화", "crash", "kernel", "panic", "신성장동력", "자체발의", "발의", "과제", "기술검토"],
    "미래:역량개발": ["스터디", "학습", "역량개발", "자기계발", "개인학습", "기술학습", "ChatGPT", "AI학습", "DX", "SW역량", "외부전시", "로테이션", "기술동향", "컨텐츠", "소통", "심리상담", "MBTI", "애니어그램", "후배육성", "REINVENT", "커뮤니케이션"]
}

# 업무유형별 예시 템플릿
WORK_TYPE_EXAMPLES = {
    "휴가": {
        "description": "해당 Sprint에 사용한 휴가",
        "summary_examples": ["연차 전일", "반차", "개인휴가"],
        "program_model_required": False,
        "biz_domain_required": False,
        "example_call": "create_work_entry('PROJ-123', '{time}', '휴가', '{summary}')",
        "note": "휴가는 자동으로 Personal Work Log 이슈에 작성됩니다."
    },
    "교육": {
        "description": "해당 Sprint에 수강한 사/내외 On/OFF-Line 교육",
        "summary_examples": ["C++ 교육", "보안 교육", "온라인 세미나 참석", "사내 교육", "사외 교육"],
        "program_model_required": False,
        "biz_domain_required": False,
        "example_call": "create_work_entry('PROJ-123', '{time}', '교육', '{summary}')",
        "note": "교육은 자동으로 Personal Work Log 이슈에 작성됩니다."
    },
    "출장": {
        "description": "해당 Sprint에 진행한 출장 (휴가와 동일한 형식으로 Work Log 입력)",
        "summary_examples": ["현장 출장", "고객사 방문", "외부 미팅"],
        "program_model_required": False,
        "biz_domain_required": False,
        "example_call": "create_work_entry('PROJ-123', '{time}', '출장', '{summary}')"
    },
    "이슈": {
        "description": "Q/인증/필드/DIT 등 각종 이슈 검토/개선 (추가: 25/01/02, TDD, DQA 등 외부 부서에서 등록한 이슈)",
        "summary_examples": ["필드 이슈 분석", "인증 문제 해결", "DIT 이슈 검토", "TDD 이슈 분석", "DQA 이슈 해결"],
        "program_model_required": True,
        "biz_domain_required": True,
        "example_call": "create_work_entry('PROJ-123', '{time}', '이슈', '{summary}', 'webOS TV 27', 'TV')"
    },
    "지원": {
        "description": "Sanity Test, 평택/구미/해생지 지원 등 SW PL 요청 및 Operation 지원 업무",
        "summary_examples": ["Sanity Test 지원", "평택 지원", "구미 지원", "해생지 지원", "운영 지원"],
        "program_model_required": True,
        "biz_domain_required": True,
        "example_call": "create_work_entry('PROJ-123', '{time}', '지원', '{summary}', 'webOS TV 27', 'TV')"
    },
    "개발": {
        "description": "이슈/지원 업무를 제외한 Non-Initiative 개발 업무 (사업부 연구소로서의 개발 업무), 팀장회의/DDM/점검회의 등 개발 회의 참석, 보드간사 등 (※ Initiative와 관련된 회의는 해당 Initiative 하위 Story/Task에 Work Log 입력)",
        "summary_examples": ["요구사항 분석 및 코드 구현", "API 개발", "팀장회의 참석", "DDM 참석", "점검회의", "보드간사 활동"],
        "program_model_required": True,
        "biz_domain_required": True,
        "example_call": "create_work_entry('PROJ-123', '{time}', '개발', '{summary}', 'webOS TV 27', 'TV')"
    },
    "운영": {
        "description": "이사, 월례조회, 각종 간사활동 (보드간사 등 개발 관련 간사 제외), JB, 청소 등 비개발 업무 활동 (※ 서버 운영 업무는 '개발'로 선택해야 함)",
        "summary_examples": ["이사 참석", "월례조회", "간사 활동", "JB 활동", "청소", "비개발 업무"],
        "program_model_required": False,
        "biz_domain_required": False,
        "example_call": "create_work_entry('PROJ-123', '{time}', '운영', '{summary}')"
    },
    "미래:TRM연계": {
        "description": "미래준비 활동 중에서 TRM과 연계된 활동, PO 리뷰 시 TRM으로 지정된 과제 (※ TRM (Technical Road Map): 사업 전략과 기술 트렌드에 맞춰 수립된 SW개발담당의 기술 로드맵)",
        "summary_examples": ["기술 로드맵 검토", "TRM 과제 수행", "PO 리뷰 참석"],
        "program_model_required": False,
        "biz_domain_required": False,
        "example_call": "create_work_entry('PROJ-123', '{time}', '미래:TRM연계', '{summary}')"
    },
    "미래:개발자발의": {
        "description": "미래준비 활동 중에서 개발자 발의 과제와 연관된 활동, 미래 준비 영역에서 기술 검토, 개발 등이 필요하다고 판단되는 과제를 자체 발의한 경우, PoC 활동, 성능 개선 활동, 보안 강화 활동, Crash/Kernel Panic 관련 강화 활동, 신성장동력 아이디어 발굴/개발, 신규 프레임워크 탐색 외",
        "summary_examples": ["PoC 개발", "성능 최적화 연구", "신기술 검토", "보안 강화 활동", "Crash 분석", "Kernel Panic 해결", "신성장동력 아이디어 발굴", "신규 프레임워크 탐색"],
        "program_model_required": False,
        "biz_domain_required": False,
        "example_call": "create_work_entry('PROJ-123', '{time}', '미래:개발자발의', '{summary}')"
    },
    "미래:역량개발": {
        "description": "미래준비 활동 중에서 개인 역량 개발과 관련된 활동, 개발자 역량 강화: 개인 역량 자주 학습, DX/SW 역량 Study, 사내/외 교육 참여, 외부 전시 참여, 업무 로테이션 활동, 기술 동향 분석 활동, ChatGPT 활용 스킬 강화, IT 관련 컨텐츠 시청 외, 커뮤니케이션 강화: 구성원 간 소통 시간, 심리 상담 (MBTI, 애니어그램 등), 후배 육성 프로그램, REINVENT DAY 활동 외",
        "summary_examples": ["개인 기술 학습", "DX/SW 역량 Study", "ChatGPT 활용 스킬 강화", "기술 동향 분석", "IT 컨텐츠 시청", "구성원 간 소통", "심리 상담", "MBTI 검사", "후배 육성", "REINVENT DAY 활동"],
        "program_model_required": False,
        "biz_domain_required": False,
        "example_call": "create_work_entry('PROJ-123', '{time}', '미래:역량개발', '{summary}')"
    }
}

def validate_work_description(work_type: str, work_summary: str, program_model: Optional[str], biz_domain: str) -> Optional[str]:
    """사내 Work Description 양식 유효성 검사"""
    if work_type not in WORK_TYPES:
        return f"잘못된 업무유형: {work_type}. 사용 가능한 값: {', '.join(WORK_TYPES)}"

    if biz_domain not in BIZ_DOMAINS:
        return f"잘못된 Biz. Domain: {biz_domain}. 사용 가능한 값: {', '.join(BIZ_DOMAINS)}"

    # "이슈", "지원", "개발" 업무유형에서만 program_model 필수 검사
    if work_type in REQUIRED_PROGRAM_MODEL_TYPES:
        if not program_model:
            return f"업무유형 '{work_type}'에는 프로그램/특화모델명이 필수입니다."
        if program_model not in VALID_PROGRAMS:
            return f"잘못된 프로그램/특화모델명: {program_model}. 사용 가능한 값: {', '.join(VALID_PROGRAMS)}"

    if not work_summary.strip():
        return "업무 내용 요약은 필수입니다."

    return None

def build_work_description(work_type: str, work_summary: str, program_model: Optional[str], biz_domain: str) -> str:
    """사내 Work Description 양식에 맞는 comment 문자열 생성"""
    parts = [work_type, work_summary]

    if program_model:
        parts.append(program_model)

    if work_type in REQUIRED_BIZ_DOMAIN_TYPES:
        parts.append(biz_domain)

    return ", ".join(parts)

def get_issue_title(issue_key: str) -> str:
    """Jira 이슈의 제목을 조회합니다."""
    try:
        url = f"{JIRA_URL}rest/api/2/issue/{issue_key}?fields=summary"
        response = requests.get(url, auth=AUTH, timeout=10, verify=False)
        response.raise_for_status()
        data = response.json()
        return data.get('fields', {}).get('summary', '제목 없음')
    except Exception:
        return '제목 조회 실패'

def find_personal_work_log_issue() -> Optional[str]:
    """현재 사용자의 Personal Work Log 이슈를 찾습니다."""
    try:
        url = f"{JIRA_URL}rest/api/2/search"
        jql = "summary ~ 'Personal Work Log' AND assignee = currentUser() AND status != Closed ORDER BY created DESC"
        params = {
            "jql": jql,
            "maxResults": 1,
            "fields": "key,summary"
        }

        response = requests.get(url, auth=AUTH, params=params, timeout=10, verify=False)
        response.raise_for_status()

        data = response.json()
        issues = data.get('issues', [])

        if issues:
            return issues[0]['key']
        else:
            return None
    except Exception:
        return None

def parse_started_date(started: str) -> str:
    """사용자 입력 날짜를 Jira API 형식으로 변환"""
    if not started:
        return None

    try:
        # 이미 완전한 ISO 형식인 경우 (.000 포함) 그대로 반환
        if 'T' in started and '.000+' in started:
            return started

        # ISO 형식이지만 .000이 없는 경우 추가
        if 'T' in started and '+' in started and '.000+' not in started:
            return started.replace('+', '.000+')

        # 날짜만 있는 경우 (YYYY-MM-DD)
        if len(started) == 10 and started.count('-') == 2:
            return f"{started}T09:00:00.000+0900"

        # 날짜와 시간이 있는 경우 (YYYY-MM-DD HH:MM)
        if ' ' in started:
            date_part, time_part = started.split(' ', 1)
            if ':' in time_part:
                return f"{date_part}T{time_part}:00.000+0900"

        return started
    except:
        return None

def match_work_types_by_keywords(description: str):
    """사용자 설명에서 키워드를 매칭하여 적합한 업무유형들을 찾습니다."""
    description_lower = description.lower()
    matches = []

    for work_type, keywords in WORK_TYPE_KEYWORDS.items():
        matched_keywords = [kw for kw in keywords if kw in description_lower]
        if matched_keywords:
            confidence = "high" if len(matched_keywords) >= 2 else "medium"
            matches.append({
                "work_type": work_type,
                "confidence": confidence,
                "matched_keywords": matched_keywords,
                "reason": f"'{', '.join(matched_keywords)}' 키워드 감지"
            })

    # 신뢰도 순으로 정렬 (high > medium)
    matches.sort(key=lambda x: x["confidence"], reverse=True)
    return matches

def generate_worklog_examples(work_type: str, user_description: str, time_spent: str = "3h") -> Dict[str, Any]:
    """특정 업무유형에 대한 worklog 작성 예시를 생성합니다."""
    if work_type not in WORK_TYPE_EXAMPLES:
        return {}

    template = WORK_TYPE_EXAMPLES[work_type]

    # 사용자 설명을 바탕으로 적절한 요약 선택 또는 생성
    suggested_summary = template["summary_examples"][0]  # 기본값

    # 사용자 설명에서 핵심 단어 추출하여 더 적절한 요약 생성 시도
    if "코드" in user_description or "개발" in user_description:
        if work_type == "개발":
            suggested_summary = "요구사항 분석 및 코드 구현"
    elif "교육" in user_description or "학습" in user_description:
        if work_type == "교육":
            suggested_summary = user_description.replace("받았", "").replace("했", "").strip()
    elif "회의" in user_description:
        if work_type == "운영":
            suggested_summary = "팀 회의 참석"

    example_call = template["example_call"].format(
        time=time_spent,
        summary=suggested_summary
    )

    return {
        "work_type": work_type,
        "suggested_summary": suggested_summary,
        "program_model_required": template["program_model_required"],
        "biz_domain_required": template["biz_domain_required"],
        "example_call": example_call,
        "all_examples": template["summary_examples"],
        "note": template.get("note", "")
    }

def parse_work_description(comment: str) -> Dict[str, Optional[str]]:
    """기존 comment에서 Work Description 파라미터들 추출"""
    if not comment:
        return {"work_type": None, "work_summary": None, "program_model": None, "biz_domain": "TV"}

    lines = comment.split('\n')
    first_line = lines[0].strip()

    if ',' not in first_line:
        return {"work_type": None, "work_summary": first_line, "program_model": None, "biz_domain": "TV"}

    parts = [part.strip() for part in first_line.split(',')]

    result = {
        "work_type": parts[0] if len(parts) > 0 else None,
        "work_summary": parts[1] if len(parts) > 1 else None,
        "program_model": parts[2] if len(parts) > 2 else None,
        "biz_domain": parts[3] if len(parts) > 3 else "TV"
    }

    return result

# MCP 서버 설정
mcp = FastMCP(
    name="Jira Sprint and Worklog Assistant",
    instructions="""Jira 스프린트(이슈)를 분석하여 worklog 해석오류와 Story Points 달성률을 검사하고
    Jira 이슈의 worklog(작업 시간 기록)를 LG전자 사내 Work Description 양식에 맞게 처리하고 분석할 수 있는 도구입니다.""",
)

@mcp.tool()
async def create_work_entry(
    issue_key: str,
    time_spent: str,
    work_type: str,
    work_summary: str,
    program_model: Optional[str] = None,
    biz_domain: str = "TV",
    started: Optional[str] = None,
    issue_title: Optional[str] = None,
    explanation: str = None
) -> Dict[str, Any]:
    """
    Jira 이슈에 LG전자 사내 Work Description 양식에 맞는 worklog를 생성합니다.

    중요: Personal Work Log 이슈에는 절대로 휴가/교육 이외의 업무유형을 작성하지 마세요!
    Personal Work Log 이슈는 오직 휴가와 교육 전용입니다.

    LG전자 사내 Work Description 양식: "업무유형, 업무 내용 요약, 프로그램/특화모델명, Biz. Domain"
    휴가/교육 업무는 자동으로 Personal Work Log 이슈에 작성됩니다.

    Args:
        issue_key: Jira 이슈 키 (ex: "PROJ-123")
                  Personal Work Log 이슈 사용 시 휴가/교육만 허용됨
        issue_title: 사용자 식별 편의를 위한 이슈 제목 (실제 기능에는 영향 없음, 함수 실행 시 자동 조회됨)
        time_spent: 소요 시간 (ex: "2h 30m", "1d", "4h")
        work_type: 업무유형 - 필수 선택
            • "개발" - 비Initiative 개발 업무 (프로그램/모델명, Biz.Domain 필수)
            • "이슈" - Q/인증/필드/DIT 등 이슈 검토/개선 (프로그램/모델명, Biz.Domain 필수)
            • "지원" - Sanity Test, 평택/구미/해생지 지원 등 (프로그램/모델명, Biz.Domain 필수)
            • "휴가" - 해당 Sprint에 사용한 휴가 (program_model, biz_domain 입력 불가, 자동으로 Personal Work Log 이슈에 작성) Personal Work Log 허용
            • "교육" - 사/내외 On/OFF-Line 교육 (program_model, biz_domain 입력 불가, 자동으로 Personal Work Log 이슈에 작성) Personal Work Log 허용
            • "출장" - 해당 Sprint에 진행한 출장 (program_model, biz_domain 입력 불가)
            • "운영" - 이사, 월례조회, 간사활동, JB, 청소 등 비개발 업무 (program_model, biz_domain 입력 불가)
            • "미래:TRM연계" - TRM과 연계된 미래준비 활동 (program_model, biz_domain 입력 불가) Personal Work Log 금지
            • "미래:개발자발의" - 개발자 발의 과제 미래준비 활동 (program_model, biz_domain 입력 불가) Personal Work Log 금지
            • "미래:역량개발" - 개인 역량 개발 미래준비 활동 (program_model, biz_domain 입력 불가) Personal Work Log 금지
        work_summary: 업무 내용 요약 (ex: "요구사항 분석", "반일", "C++ 교육")
        program_model: 프로그램/특화모델명 - "개발"/"이슈"/"지원" 업무에서만 필수 (다른 업무유형에서는 입력해도 자동 무시)
            사용 가능한 값: "ID_2023", "ID_2024", "ID_2025", "ID_2026", "ID_2027", "IT_2023", "IT_2024", "IT_2025", "IT_2026", "IT_2027", "Audio_2023", "Audio_2024", "Audio_2025", "Audio_2026", "Audio_2027", "Common", "ServerApp", "webOS TV 27", "webOS TV 26", "webOS TV 25", "webOS TV 24", "webOS TV 23", "webOS TV 22", "webOS TV 6.0", "webOS TV 5.0", "webOS TV 4.5", "webOS TV 4.0", "webOS TV 3.5", "webOS TV 3.0", "webOS TV 2.0", "webOS TV 1.0", "오디오S7", "무선M6", "무선W6", "PetCareZone", "Layer", "StanByMe", "StanByMe2", "StanByMeGo", "StanbyMEII", "StanbyME2_4K", "무선AV", "무선M4", "아뜰리에", "MeMor", "PINE22", "PINE23", "PINE24", "PINE25", "PUMA", "벤더블", "스타워즈", "BASKIN", "CAT2", "SPK", "T-Native", "SeniorTV", "WEE3.0", "WEE", "WEE1.0", "WEE2.0", "WEE2.0S", "WEE2.0SS", "WEE2.0M", "WEE2.5M"
            • TV: "StanbyME", "WEE", "무선AV", "아뜨리에", "MeMor", "PINE22", "PUMA", "OLD", "ServerApp", "Common"
            • webOS: "webOS22 Initial", "webOS6.0 MR1", "webOS5.0 MR2" 등
            • 다른 사업부: "ID_2025", "IT_2024", "Audio_2026" 등 (format: 사업부_YYYY)
        biz_domain: Biz. Domain - "개발"/"이슈"/"지원" 업무에서만 사용 (다른 업무유형에서는 입력해도 자동으로 "TV"로 설정)
            사용 가능한 값: "TV", "ID", "IT", "Audio", "Platform Biz", "Common", "ES", "VS", "HS", "Others"
            • "TV" (기본값) - TV 사업부 제품을 위한 업무
            • "ID" - ID 사업부 제품을 위한 업무
            • "IT" - IT 사업부 제품을 위한 업무
            • "Audio" - Audio 사업부 제품을 위한 업무
            • "Platform Biz" - 플랫폼 사업을 위한 업무
            • "Common" - 전체 사업부 공통 업무
        started: 작업 시작 시간 ISO 8601 형식 (선택사항, 기본값: 현재 시간)

    Examples:
        - 개발 업무: create_work_entry("PROJ-123", "2h", "개발", "요구사항 분석", "ID_2025", "ID")
        - 휴가: create_work_entry("PROJ-123", "4h", "휴가", "반일")  # → 자동으로 Personal Work Log 이슈에 작성
        - 교육: create_work_entry("PROJ-123", "8h", "교육", "C++ 교육")  # → 자동으로 Personal Work Log 이슈에 작성
        - 이슈: create_work_entry("PROJ-123", "1h", "이슈", "필드 이슈 검토", "webOS TV 5.0", "TV")
        - 운영: create_work_entry("PROJ-123", "2h", "운영", "파트 회의")
        - 미래:역량개발: create_work_entry("PROJ-123", "6h", "미래:역량개발", "MCP server 구현")
        - 잘못 입력해도 자동 수정: create_work_entry("PROJ-123", "4h", "휴가", "반일", "webOS TV 27", "ID")  # → program_model과 biz_domain 자동 무시, Personal Work Log에 작성
    """
    try:
        original_issue_key = issue_key
        original_issue_title = get_issue_title(issue_key)

        # Personal Work Log 이슈 여부 확인
        is_personal_work_issue = original_issue_title.startswith('Personal Work Log')

        # Personal Work Log 이슈에는 휴가/교육만 작성 가능하도록 강력한 검증
        if is_personal_work_issue and work_type not in PERSONAL_WORK_TYPES:
            return {
                "status": "error",
                "message": f"Personal Work Log 이슈에는 휴가/교육 업무만 작성할 수 있습니다. '{work_type}' 업무는 일반 스프린트 이슈에 작성해주세요. 허용되는 업무유형: {', '.join(PERSONAL_WORK_TYPES)}"
            }

        # 휴가나 교육 업무유형인 경우 Personal Work Log 이슈로 변경
        if work_type in PERSONAL_WORK_TYPES:
            personal_issue_key = find_personal_work_log_issue()
            if personal_issue_key:
                issue_key = personal_issue_key
                issue_title = get_issue_title(issue_key)
            else:
                return {
                    "status": "error",
                    "message": f"휴가/교육 업무는 Personal Work Log 이슈에 작성되어야 하지만, Personal Work Log 이슈를 찾을 수 없습니다. 현재 사용자에게 할당된 'Personal Work Log' 이슈가 있는지 확인해주세요."
                }
        else:
            issue_title = original_issue_title

        # "이슈", "지원", "개발" 이외의 업무유형에서는 program_model과 biz_domain 자동 조정
        if work_type not in REQUIRED_PROGRAM_MODEL_TYPES:
            program_model = None  # 불필요한 program_model 제거
        if work_type not in REQUIRED_BIZ_DOMAIN_TYPES:
            biz_domain = "TV"  # 기본값으로 설정

        # Work Description 양식 유효성 검사
        validation_error = validate_work_description(work_type, work_summary, program_model, biz_domain)
        if validation_error:
            return {"status": "error", "message": f"양식 오류: {validation_error}"}

        # Work Description 양식에 맞는 comment 생성
        comment = build_work_description(work_type, work_summary, program_model, biz_domain)

        url = f"{JIRA_URL}rest/api/2/issue/{issue_key}/worklog"
        payload = {
            "timeSpent": time_spent,
            "comment": comment
        }

        # started 필드가 있으면 먼저 포함해서 시도
        success_method = "현재 시간"
        if started:
            parsed_started = parse_started_date(started)
            if parsed_started:
                payload["started"] = parsed_started
                response = requests.post(url, json=payload, auth=AUTH, timeout=10, verify=False)

                if response.ok:
                    success_method = f"지정된 시간 ({started})"
                else:
                    # started 필드 제거하고 재시도
                    payload.pop("started", None)
                    response = requests.post(url, json=payload, auth=AUTH, timeout=10, verify=False)
            else:
                response = requests.post(url, json=payload, auth=AUTH, timeout=10, verify=False)
        else:
            response = requests.post(url, json=payload, auth=AUTH, timeout=10, verify=False)

        # 상세 에러 로깅
        if not response.ok:
            error_detail = response.text
            return {
                "status": "error",
                "message": f"HTTP {response.status_code} 에러: {error_detail}",
                "request_payload": payload
            }

        response.raise_for_status()

        # 성공 메시지 생성
        if work_type in PERSONAL_WORK_TYPES:
            success_message = f"휴가/교육 업무로 Personal Work Log 이슈 {issue_key} ({issue_title})에 worklog가 성공적으로 생성되었습니다. (원래 요청: {original_issue_key}, 생성 시간: {success_method})"
        else:
            success_message = f"이슈 {issue_key} ({issue_title})에 worklog가 성공적으로 생성되었습니다. (생성 시간: {success_method})"

        return {
            "status": "success",
            "message": success_message,
            "data": response.json()
        }
    except requests.exceptions.RequestException as e:
        return {"status": "error", "message": f"Worklog 생성 실패: {e}"}
    except Exception as e:
        return {"status": "error", "message": f"예상치 못한 오류: {e}"}

@mcp.tool()
async def get_work_logs(issue_key: str, issue_title: Optional[str] = None, explanation: str = None) -> Dict[str, Any]:
    """
    Jira 이슈의 모든 worklog를 조회합니다.

    Args:
        issue_key: Jira 이슈 키 (예: PROJ-123)
        issue_title: 사용자 식별 편의를 위한 이슈 제목 (실제 기능에는 영향 없음, 함수 실행 시 자동 조회됨)
    """
    try:
        # 이슈 제목 조회
        issue_title = get_issue_title(issue_key)

        url = f"{JIRA_URL}rest/api/2/issue/{issue_key}/worklog"
        response = requests.get(url, auth=AUTH, timeout=10, verify=False)
        response.raise_for_status()

        data = response.json()
        worklogs = data.get('worklogs', [])

        return {
            "status": "success",
            "message": f"이슈 {issue_key} ({issue_title})의 worklog {len(worklogs)}개를 조회했습니다.",
            "data": {
                "total": len(worklogs),
                "worklogs": worklogs
            }
        }
    except requests.exceptions.RequestException as e:
        return {"status": "error", "message": f"Worklog 조회 실패: {e}"}
    except Exception as e:
        return {"status": "error", "message": f"예상치 못한 오류: {e}"}

@mcp.tool()
async def get_single_work_log(issue_key: str, worklog_id: str, expand: str = "properties", issue_title: Optional[str] = None, explanation: str = None) -> Dict[str, Any]:
    """
    Jira 이슈의 특정 worklog를 조회합니다.

    Args:
        issue_key: Jira 이슈 키 (예: PROJ-123)
        worklog_id: 조회할 worklog ID
        expand: 추가 정보 포함 (기본값: properties)
        issue_title: 사용자 식별 편의를 위한 이슈 제목 (실제 기능에는 영향 없음, 함수 실행 시 자동 조회됨)
    """
    try:
        # 이슈 제목 조회
        issue_title = get_issue_title(issue_key)

        url = f"{JIRA_URL}rest/api/2/issue/{issue_key}/worklog/{worklog_id}"
        params = {"expand": expand} if expand else {}

        response = requests.get(url, auth=AUTH, params=params, timeout=10, verify=False)
        response.raise_for_status()

        return {
            "status": "success",
            "message": f"이슈 {issue_key} ({issue_title})의 worklog {worklog_id}를 조회했습니다.",
            "data": response.json()
        }
    except requests.exceptions.RequestException as e:
        return {"status": "error", "message": f"Worklog 조회 실패: {e}"}
    except Exception as e:
        return {"status": "error", "message": f"예상치 못한 오류: {e}"}

@mcp.tool()
async def modify_work_entry(
    issue_key: str,
    worklog_id: str,
    time_spent: Optional[str] = None,
    work_type: Optional[str] = None,
    work_summary: Optional[str] = None,
    program_model: Optional[str] = None,
    biz_domain: Optional[str] = None,
    started: Optional[str] = None,
    issue_title: Optional[str] = None,
    explanation: str = None
) -> Dict[str, Any]:
    """
    Jira 이슈의 특정 worklog를 LG전자 사내 Work Description 양식에 맞게 변경합니다.

    기존 worklog의 Work Description을 파싱하여 제공된 파라미터만 업데이트합니다.

    Args:
        issue_key: Jira 이슈 키 (ex: "PROJ-123")
        worklog_id: 변경할 worklog ID
        issue_title: 사용자 식별 편의를 위한 이슈 제목 (실제 기능에는 영향 없음, 함수 실행 시 자동 조회됨)
        time_spent: 변경할 소요 시간 (선택사항, ex: "3h", "1d")
        work_type: 변경할 업무유형 (선택사항, create_work_entry의 work_type 참조)
        work_summary: 변경할 업무 내용 요약 (선택사항)
        program_model: 변경할 프로그램/특화모델명 (선택사항, create_work_entry의 program_model 참조)
        biz_domain: 변경할 Biz. Domain (선택사항, create_work_entry의 biz_domain 참조)
        started: 변경할 작업 시작 시간 (선택사항)

    Note:
        - 제공되지 않은 파라미터는 기존 값을 유지합니다.
        - Work Description 양식 유효성 검사를 수행합니다.
    """
    try:
        # 이슈 제목 조회
        issue_title = get_issue_title(issue_key)

        url = f"{JIRA_URL}rest/api/2/issue/{issue_key}/worklog/{worklog_id}"

        # 기존 worklog 정보 조회
        get_response = requests.get(url, auth=AUTH, timeout=10, verify=False)
        get_response.raise_for_status()
        existing_worklog = get_response.json()

        # 기존 comment에서 Work Description 파라미터 추출
        existing_comment = existing_worklog.get("comment", "")
        parsed = parse_work_description(existing_comment)

        # 새로운 값들로 업데이트 (제공되지 않은 값은 기존 값 유지)
        final_work_type = work_type or parsed["work_type"]
        final_work_summary = work_summary or parsed["work_summary"]
        final_program_model = program_model if program_model is not None else parsed["program_model"]
        final_biz_domain = biz_domain or parsed["biz_domain"]

        if final_work_type in ["휴가", "교육", "출장", "미래:역량개발"]:
            final_program_model = None  # 불필요한 program_model 제거
            final_biz_domain = None  # 불필요한 biz_domain 제거

        # Work Description 양식 유효성 검사 (새로운 값이 있는 경우만)
        if any([work_type, work_summary, program_model is not None, biz_domain]):
            if final_work_type and final_work_summary:
                validation_error = validate_work_description(final_work_type, final_work_summary, final_program_model, final_biz_domain)
                if validation_error:
                    return {"status": "error", "message": f"양식 오류: {validation_error}"}

        # comment 생성
        if any([work_type, work_summary, program_model is not None, biz_domain]):
            if final_work_type and final_work_summary:
                final_comment = build_work_description(final_work_type, final_work_summary, final_program_model, final_biz_domain)
            else:
                final_comment = existing_comment
        else:
            final_comment = existing_comment

        # started 처리 - 새로 입력된 값이 있으면 파싱, 없으면 기존 값 사용
        final_started = existing_worklog.get("started")
        if started:
            parsed_started = parse_started_date(started)
            if parsed_started:
                final_started = parsed_started

        # 수정할 데이터 준비
        payload = {
            "timeSpent": time_spent or existing_worklog.get("timeSpent"),
            "comment": final_comment,
            "started": final_started
        }

        response = requests.put(url, json=payload, auth=AUTH, timeout=10, verify=False)

        # 상세 에러 로깅
        if not response.ok:
            error_detail = response.text
            return {
                "status": "error",
                "message": f"HTTP {response.status_code} 에러: {error_detail}",
                "request_payload": payload
            }

        response.raise_for_status()

        return {
            "status": "success",
            "message": f"이슈 {issue_key} ({issue_title})의 worklog {worklog_id}가 성공적으로 변경되었습니다.",
            "data": response.json()
        }
    except requests.exceptions.RequestException as e:
        return {"status": "error", "message": f"Worklog 변경 실패: {e}"}
    except Exception as e:
        return {"status": "error", "message": f"예상치 못한 오류: {e}"}

@mcp.tool()
async def remove_work_entry(issue_key: str, worklog_id: str, issue_title: Optional[str] = None, explanation: str = None) -> Dict[str, Any]:
    """
    Jira 이슈의 특정 worklog를 제거합니다.

    Args:
        issue_key: Jira 이슈 키 (예: PROJ-123)
        worklog_id: 제거할 worklog ID
        issue_title: 사용자 식별 편의를 위한 이슈 제목 (실제 기능에는 영향 없음, 함수 실행 시 자동 조회됨)
    """
    try:
        # 이슈 제목 조회
        issue_title = get_issue_title(issue_key)

        url = f"{JIRA_URL}rest/api/2/issue/{issue_key}/worklog/{worklog_id}"
        response = requests.delete(url, auth=AUTH, timeout=10, verify=False)
        response.raise_for_status()

        return {
            "status": "success",
            "message": f"이슈 {issue_key} ({issue_title})의 worklog {worklog_id}가 성공적으로 제거되었습니다."
        }
    except requests.exceptions.RequestException as e:
        return {"status": "error", "message": f"Worklog 제거 실패: {e}"}
    except Exception as e:
        return {"status": "error", "message": f"예상치 못한 오류: {e}"}

@mcp.tool()
async def get_worklog_guide(
    work_description: str,
    time_spent: Optional[str] = "3h",
    explanation: str = None
) -> Dict[str, Any]:
    """
    사용자의 업무 설명을 바탕으로 적절한 worklog 작성 방법을 안내합니다.

    Args:
        work_description: 사용자가 한 일에 대한 설명 (ex: "코드 개발했어", "교육 받았어", "회의 참석했어")
        time_spent: 소요 시간 (선택사항, 기본값: "3h")

    Returns:
        추천 업무유형, 작성 예시, 주의사항 등을 포함한 가이드

    Examples:
        - get_worklog_guide("오늘 3시간 코드 개발했어")
        - get_worklog_guide("교육 받았는데 어떻게 작성해야 해?")
        - get_worklog_guide("팀 회의 참석했어", "2h")
    """
    try:
        # 키워드 매칭으로 적합한 업무유형들 찾기
        matched_types = match_work_types_by_keywords(work_description)

        if not matched_types:
            # 매칭되는 키워드가 없는 경우 일반적인 가이드 제공
            return {
                "status": "success",
                "message": "구체적인 업무유형을 찾지 못했습니다. 아래 일반 가이드를 참고하세요.",
                "data": {
                    "suggested_work_types": [],
                    "all_work_types": list(WORK_TYPE_EXAMPLES.keys()),
                    "general_tips": [
                        "업무 내용을 더 구체적으로 설명해주세요 (예: '코드 개발', '교육 참석', '회의 참석')",
                        "개발/이슈/지원 업무는 프로그램/모델명과 Biz.Domain이 필수입니다",
                        "휴가/교육은 자동으로 Personal Work Log 이슈에 작성됩니다",
                        "첫 줄에는 콤마(,)를 사용하지 마세요"
                    ],
                    "format": "업무유형, 업무 내용 요약, 프로그램/특화모델명, Biz. Domain"
                }
            }

        # 매칭된 업무유형들에 대한 예시 생성
        suggestions = []
        for match in matched_types[:3]:  # 상위 3개만
            work_type = match["work_type"]
            example = generate_worklog_examples(work_type, work_description, time_spent)
            if example:
                suggestions.append({
                    "work_type": work_type,
                    "confidence": match["confidence"],
                    "reason": match["reason"],
                    "suggested_summary": example["suggested_summary"],
                    "program_model_required": example["program_model_required"],
                    "biz_domain_required": example["biz_domain_required"],
                    "example_call": example["example_call"],
                    "other_examples": example["all_examples"],
                    "note": example["note"]
                })

        return {
            "status": "success",
            "message": f"'{work_description}' 설명을 분석하여 {len(suggestions)}개의 적합한 업무유형을 찾았습니다.",
            "data": {
                "user_input": {
                    "description": work_description,
                    "time_spent": time_spent
                },
                "suggested_work_types": suggestions,
                "general_tips": [
                    "가장 적합한 업무유형을 선택하세요",
                    "프로그램/모델명이 필요한 경우 적절한 값을 입력하세요 (예: 'webOS TV 27', 'ID_2025')",
                    "Biz.Domain이 필요한 경우 적절한 값을 입력하세요 (예: 'TV', 'ID', 'IT')",
                    "휴가/교육은 자동으로 Personal Work Log에 작성됩니다",
                    "업무 내용 요약에는 콤마(,)를 사용하지 마세요"
                ],
                "format_reminder": "업무유형, 업무 내용 요약, 프로그램/특화모델명, Biz. Domain"
            }
        }

    except Exception as e:
        return {
            "status": "error",
            "message": f"Worklog 가이드 생성 중 오류 발생: {e}"
        }

def parse_time_spent(time_str: str) -> float:
    """Jira timeSpent 문자열을 시간(hour)으로 변환"""
    if not time_str or not time_str.strip():
        return 0.0

    time_str = time_str.strip().lower()
    total_hours = 0.0

    # 시간 단위별 변환 규칙 (1w=40h, 1d=8h, 1h=1h, 1m=1/60h)
    patterns = [
        (r'(\d+(?:\.\d+)?)w', 40),    # 주
        (r'(\d+(?:\.\d+)?)d', 8),     # 일
        (r'(\d+(?:\.\d+)?)h', 1),     # 시간
        (r'(\d+(?:\.\d+)?)m', 1/60),  # 분
    ]

    for pattern, multiplier in patterns:
        matches = re.findall(pattern, time_str)
        for match in matches:
            total_hours += float(match) * multiplier

    return round(total_hours, 2)

def get_issue_title(issue_key: str) -> str:
    """Jira 이슈의 제목을 조회합니다."""
    try:
        url = f"{JIRA_URL}rest/api/2/issue/{issue_key}?fields=summary"
        response = requests.get(url, auth=AUTH, timeout=10, verify=False)
        response.raise_for_status()
        data = response.json()
        return data.get('fields', {}).get('summary', '제목 없음')
    except Exception:
        return '제목 조회 실패'

def parse_work_description(comment: str) -> Dict[str, Optional[str]]:
    """기존 comment에서 Work Description 파라미터들 추출"""
    if not comment:
        return {"work_type": None, "work_summary": None, "program_model": None, "biz_domain": "TV"}

    lines = comment.split('\n')
    first_line = lines[0].strip()

    if ',' not in first_line:
        return {"work_type": None, "work_summary": first_line, "program_model": None, "biz_domain": "TV"}

    parts = [part.strip() for part in first_line.split(',')]

    result = {
        "work_type": parts[0] if len(parts) > 0 else None,
        "work_summary": parts[1] if len(parts) > 1 else None,
        "program_model": parts[2] if len(parts) > 2 else None,
        "biz_domain": parts[3] if len(parts) > 3 else "TV"
    }

    return result

def validate_work_description(work_type: str, work_summary: str, program_model: Optional[str], biz_domain: str) -> Optional[str]:
    """사내 Work Description 양식 유효성 검사"""
    if work_type not in WORK_TYPES:
        return f"잘못된 업무유형: {work_type}"

    if biz_domain not in BIZ_DOMAINS:
        return f"잘못된 Biz. Domain: {biz_domain}"

    if work_type in REQUIRED_PROGRAM_MODEL_TYPES:
        if not program_model:
            return f"업무유형 '{work_type}'에는 프로그램/특화모델명이 필수입니다"
        if program_model not in VALID_PROGRAMS:
            return f"잘못된 프로그램/특화모델명: {program_model}. 사용 가능한 값: {', '.join(VALID_PROGRAMS)}"

    if not work_summary or not work_summary.strip():
        return "업무 내용 요약은 필수입니다"

    return None

def analyze_worklog_errors(worklogs: List[Dict]) -> List[Dict[str, Any]]:
    """Worklog들의 해석 오류를 분석"""
    errors = []

    for worklog in worklogs:
        comment = worklog.get('comment', '')
        if not comment:
            continue

        parsed = parse_work_description(comment)

        # Work Description 양식이 있는 경우만 검증
        if parsed['work_type']:
            validation_error = validate_work_description(
                parsed['work_type'],
                parsed['work_summary'] or '',
                parsed['program_model'],
                parsed['biz_domain']
            )

            if validation_error:
                errors.append({
                    'worklog_id': worklog['id'],
                    'author': worklog.get('author', {}).get('displayName', 'Unknown'),
                    'comment': comment,
                    'error': validation_error,
                    'time_spent': worklog.get('timeSpent', '0h')
                })

    return errors

def parse_sprint_period_from_title(title: str) -> Dict[str, Any]:
    """제목에서 스프린트 기간 파싱 (ex: '[2025_IR3SP21(6/30-7/11)]')"""
    if not title:
        return {
            'start_date': None,
            'end_date': None,
            'duration_days': 0,
            'sprint_name': None
        }

    # 정규식으로 스프린트 정보 추출: [YYYY_스프린트명(M/D-M/D)]
    pattern = r'\[(\d{4})_([^(]+)\((\d{1,2})/(\d{1,2})-(\d{1,2})/(\d{1,2})\)\]'
    match = re.search(pattern, title)

    if not match:
        return {
            'start_date': None,
            'end_date': None,
            'duration_days': 0,
            'sprint_name': None
        }

    year, sprint_name, start_month, start_day, end_month, end_day = match.groups()

    try:
        # 날짜 객체 생성
        start_date = datetime(int(year), int(start_month), int(start_day))
        end_date = datetime(int(year), int(end_month), int(end_day))

        # 연도가 바뀌는 경우 처리 (12월 -> 1월)
        if end_date < start_date:
            end_date = datetime(int(year) + 1, int(end_month), int(end_day))

        duration_days = (end_date - start_date).days + 1

        return {
            'start_date': start_date.strftime('%Y-%m-%d'),
            'end_date': end_date.strftime('%Y-%m-%d'),
            'duration_days': duration_days,
            'sprint_name': sprint_name.strip()
        }

    except ValueError:
        return {
            'start_date': None,
            'end_date': None,
            'duration_days': 0,
            'sprint_name': sprint_name.strip() if 'sprint_name' in locals() else None
        }

def calculate_work_days(worklogs: List[Dict]) -> int:
    """워크로그에서 실제 작업한 날짜 수 계산"""
    if not worklogs:
        return 0

    work_dates = set()
    for worklog in worklogs:
        started = worklog.get('started')
        if started:
            date_str = started.split('T')[0]
            work_dates.add(date_str)

    return len(work_dates)

def calculate_sp_metrics(story_points: Optional[float], worklogs: List[Dict]) -> Dict[str, Any]:
    """Story Points 메트릭 계산"""
    # 실제 작업 시간 계산
    actual_hours = 0.0
    for worklog in worklogs:
        time_spent = worklog.get('timeSpent', '')
        actual_hours += parse_time_spent(time_spent)

    # Story Points 기반 계획 시간 계산 (1SP = 4시간)
    expected_hours = (story_points * 4) if story_points else 0
    shortage_hours = expected_hours - actual_hours
    completion_rate = (actual_hours / expected_hours * 100) if expected_hours > 0 else 0

    return {
        'story_points': story_points,
        'expected_hours': expected_hours,
        'actual_hours': actual_hours,
        'shortage_hours': round(shortage_hours, 2),
        'completion_rate': round(completion_rate, 1)
    }

def get_current_user() -> str:
    """현재 Jira 사용자 조회"""
    try:
        url = f"{JIRA_URL}rest/api/2/myself"
        response = requests.get(url, auth=AUTH, timeout=10, verify=False)
        response.raise_for_status()
        data = response.json()
        return data.get('name') or data.get('key') or 'jaehyung1.lee'
    except Exception:
        return 'jaehyung1.lee'  # fallback

def calculate_current_sprint(reference_date: Optional[datetime] = None, offset: int = 0) -> Dict[str, str]:
    """스프린트 정보 계산 (2주 단위, 마지막 토일 제외)

    Args:
        reference_date: 기준 날짜 (None이면 현재 날짜 사용)
        offset: 스프린트 오프셋 (0=현재, 1=다음, -1=이전)
    """
    try:
        # 기준 스프린트: 2025_IR3SP23 (7/28-8/8)
        base_date = datetime(2025, 7, 28)
        base_end_date = datetime(2025, 8, 8)
        base_sprint_num = 23

        target_date = reference_date or datetime.now()

        # 현재 날짜가 속한 스프린트 찾기
        if target_date <= base_end_date:
            # 기준 스프린트 이전 계산
            days_before = (base_date - target_date).days
            sprint_diff = -(days_before // 14 + (1 if days_before % 14 > 0 else 0))
        else:
            # 기준 스프린트 이후 계산
            days_after = (target_date - base_end_date).days - 1
            sprint_diff = (days_after // 14) + 1

        # offset 적용
        sprint_diff += offset

        current_sprint_num = base_sprint_num + sprint_diff
        current_start = base_date + timedelta(days=sprint_diff * 14)

        # 2주(14일) - 마지막 토일(2일) = 12일 (금요일까지)
        current_end = current_start + timedelta(days=11)

        sprint_name = f"2025_IR3SP{current_sprint_num}({current_start.month}/{current_start.day}-{current_end.month}/{current_end.day})"

        return {
            'start_date': current_start.strftime('%Y-%m-%d'),
            'end_date': (current_start + timedelta(days=11)).strftime('%Y-%m-%d'),
            'sprint_name': sprint_name
        }
    except Exception:
        # fallback to original values
        return {
            'start_date': '2025-07-28',
            'end_date': '2025-08-09',
            'sprint_name': '2025_IR3SP23(7/28-8/8)'
        }

def build_dynamic_jql(reference_date: Optional[datetime] = None, offset: int = 0) -> str:
    """동적 JQL 쿼리 생성

    Args:
        reference_date: 기준 날짜 (None이면 현재 날짜 사용)
        offset: 스프린트 오프셋 (0=현재, 1=다음, -1=이전)
    """
    user = get_current_user()
    sprint_info = calculate_current_sprint(reference_date, offset)

    if offset == 0:
        # 현재 스프린트: 기존 로직 유지 (진행중이므로 >= 조건)
        return f'status not in (closed) AND (worklogAuthor in ({user}) AND worklogDate >= {sprint_info["start_date"]} OR assignee in ({user}) AND (createdDate >= {sprint_info["start_date"]} OR sprint = "{sprint_info["sprint_name"]}" OR summary ~ Personal)) AND issuetype in (Initiative, Epic, Story, Task) ORDER BY created DESC'
    else:
        # 과거/미래 스프린트: 범위 쿼리 사용
        return f'(worklogAuthor in ({user}) AND ((worklogDate >= {sprint_info["start_date"]}) AND (worklogDate < {sprint_info["end_date"]})) OR assignee in ({user}) AND (((createdDate >= {sprint_info["start_date"]}) AND (createdDate < {sprint_info["end_date"]})) OR sprint = "{sprint_info["sprint_name"]}" OR summary ~ Personal)) AND issuetype in (Initiative, Epic, Story, Task) ORDER BY created DESC'

# 상수 정의
EPIC_LINK_FIELD = "customfield_10434"  # Epic Link 필드
SPRINT_FIELD = "customfield_10005"     # Sprint 필드
DEFAULT_EPIC_KEY = "TVPLAT-534767"     # AIS - Planned 업무 Work Log Epic

def find_sprint_id_by_name(sprint_name: str) -> Optional[int]:
    """스프린트 이름으로 스프린트 ID를 찾습니다."""
    try:
        # 넓은 범위 JQL로 스프린트 이슈 검색
        search_url = f"{JIRA_URL}rest/api/2/search"
        params = {
            'jql': f'sprint = "{sprint_name}"',
            'fields': 'customfield_10005',
            'maxResults': 1
        }

        response = requests.get(search_url, auth=AUTH, params=params, timeout=10, verify=False)
        response.raise_for_status()
        data = response.json()

        issues = data.get('issues', [])
        if issues:
            fields = issues[0].get('fields', {})
            if fields:
                sprint_field = fields.get('customfield_10005')
                if sprint_field:
                    # 스프린트 필드가 list인 경우 직접 사용, dict인 경우 value 키 사용
                    sprint_values = sprint_field if isinstance(sprint_field, list) else sprint_field.get('value', []) if isinstance(sprint_field, dict) else []

                    for sprint_str in sprint_values:
                        if sprint_name in sprint_str:
                            import re
                            match = re.search(r'id=(\d+)', sprint_str)
                            if match:
                                return int(match.group(1))
        return None
    except Exception:
        return None

# 보드 ID 캐시
_board_id_cache = None

def get_board_id(board_name: str = "webOSTV_Agile_BD") -> Optional[int]:
    """보드 이름으로 보드 ID를 찾습니다."""
    global _board_id_cache
    if _board_id_cache:
        return _board_id_cache

    try:
        url = f"{JIRA_URL}rest/agile/1.0/board"
        response = requests.get(url, auth=AUTH, timeout=10, verify=False)
        response.raise_for_status()

        boards = response.json().get('values', [])
        for board in boards:
            if board_name in board.get('name', ''):
                _board_id_cache = board['id']
                return board['id']
    except Exception:
        pass
    return None

def get_sprint_id_dynamically(sprint_name: str) -> Optional[int]:
    """동적으로 스프린트 ID를 찾습니다."""
    # 1. Agile API로 보드에서 스프린트 찾기
    board_id = get_board_id()
    if board_id:
        try:
            url = f"{JIRA_URL}rest/agile/1.0/board/{board_id}/sprint"
            response = requests.get(url, auth=AUTH, timeout=10, verify=False)
            response.raise_for_status()

            sprints = response.json().get('values', [])
            for sprint in sprints:
                if sprint.get('name') == sprint_name:
                    return sprint['id']
        except Exception:
            pass

    # 2. 기존 방법으로 fallback
    sprint_id = find_sprint_id_by_name(sprint_name)
    if sprint_id:
        return sprint_id

def get_next_sprint_info(offset: int = 1) -> Dict[str, Any]:
    """다음 스프린트 정보를 반환합니다."""
    sprint_info = calculate_current_sprint(offset=offset)

    # 동적으로 스프린트 ID 찾기
    sprint_name = sprint_info['sprint_name']
    sprint_id = get_sprint_id_dynamically(sprint_name)

    if sprint_id:
        sprint_info['sprint_id'] = sprint_id
    else:
        # 최종 fallback: 추정값 사용
        current_sprint_id = 25699
        sprint_info['sprint_id'] = current_sprint_id + offset

    return sprint_info

def update_sprint_in_title(title: str, new_sprint_name: str) -> str:
    """제목에서 스프린트 정보를 업데이트합니다."""
    import re
    # [YYYY_스프린트명(날짜)] 패턴 찾기
    pattern = r'\[\d{4}_[^(]+\([^)]+\)\]'
    new_title = re.sub(pattern, f'[{new_sprint_name}]', title)
    return new_title

def create_jira_issue(fields: Dict) -> Dict[str, Any]:
    """Jira 이슈를 생성합니다."""
    try:
        url = f"{JIRA_URL}rest/api/2/issue"
        payload = {"fields": fields}

        response = requests.post(url, json=payload, auth=AUTH, timeout=15, verify=False)

        if not response.ok:
            error_detail = f"HTTP {response.status_code}: {response.text}"
            return {
                "success": False,
                "issue_key": None,
                "issue_id": None,
                "issue_url": None,
                "error": error_detail
            }

        data = response.json()
        return {
            "success": True,
            "issue_key": data.get('key'),
            "issue_id": data.get('id'),
            "issue_url": f"{JIRA_URL}browse/{data.get('key')}",
            "error": None
        }
    except Exception as e:
        return {
            "success": False,
            "issue_key": None,
            "issue_id": None,
            "issue_url": None,
            "error": str(e)
        }

@mcp.tool()
async def get_current_sprint_issues(
    jql_query: str = None,
    max_results: int = 50,
    reference_date: str = None,
    offset: int = 0,
    explanation: str = None
) -> Dict[str, Any]:
    """
    현재 할당된 스프린트의 이슈 키들을 JQL 쿼리로 가져옵니다.

    주요 기능:
    1. JQL 쿼리를 실행하여 현재 스프린트 관련 이슈들을 검색
    2. 이슈 키, 제목, 타입, 상태 등 기본 정보 제공
    3. 'Personal Work Log'로 시작하는 이슈는 휴가/교육용 스프린트로 분류
    4. 사용자와 날짜를 동적으로 계산하여 현재 스프린트 자동 감지

    Args:
        jql_query: 실행할 JQL 쿼리 (기본값: None - 동적으로 생성)
        max_results: 최대 결과 수 (기본값: 50)
        reference_date: 기준 날짜 (ISO 형식, None이면 현재 날짜 사용)
        offset: 스프린트 오프셋 (0=현재, 1=다음, -1=이전)

    Returns:
        현재 스프린트 이슈 목록:
        - issues: 이슈 목록 (키, 제목, 타입, 상태, 휴가/교육 여부)
        - total_count: 총 이슈 수
        - personal_work_count: 휴가/교육용 이슈 수
        - regular_work_count: 일반 업무 이슈 수
        - current_user: 현재 사용자
        - current_sprint: 현재 스프린트 정보
        - summary: 요약 정보

    Note:
        - 'Personal Work Log'로 시작하는 이슈는 휴가, 교육 등을 위한 개인 작업 로그 스프린트입니다.
        - 이러한 이슈들은 일반적인 개발 업무와 구분하여 관리됩니다.
        - jql_query가 None이면 현재 사용자와 스프린트를 자동으로 감지하여 쿼리를 생성합니다.
    """
    try:
        # 기준 날짜 파싱
        parsed_date = None
        if reference_date:
            try:
                parsed_date = datetime.fromisoformat(reference_date.replace('Z', '+00:00'))
            except:
                parsed_date = None

        # JQL 쿼리 동적 생성 (기본값이 None인 경우)
        if jql_query is None:
            jql_query = build_dynamic_jql(parsed_date, offset)

        # 현재 사용자와 스프린트 정보 가져오기
        current_user = get_current_user()
        current_sprint = calculate_current_sprint(parsed_date, offset)

        # JQL 검색 API 호출
        search_url = f"{JIRA_URL}rest/api/2/search"
        params = {
            'jql': jql_query,
            'fields': 'key,summary,issuetype,status,assignee',
            'maxResults': max_results
        }

        response = requests.get(search_url, auth=AUTH, params=params, timeout=15, verify=False)
        response.raise_for_status()

        data = response.json()
        issues_data = data.get('issues', [])
        total_count = data.get('total', 0)

        # 이슈 정보 파싱 및 분류
        issues = []
        personal_work_count = 0
        regular_work_count = 0

        for issue in issues_data:
            key = issue.get('key', '')
            fields = issue.get('fields', {})
            summary = fields.get('summary', '')
            issuetype = fields.get('issuetype', {}).get('name', '')
            status = fields.get('status', {}).get('name', '')
            assignee = fields.get('assignee')
            assignee_name = assignee.get('displayName', '') if assignee else 'Unassigned'

            # Personal Work Log 여부 확인
            is_personal_work = summary.startswith('Personal Work Log')
            if is_personal_work:
                personal_work_count += 1
            else:
                regular_work_count += 1

            issues.append({
                'key': key,
                'summary': summary,
                'issuetype': issuetype,
                'status': status,
                'assignee': assignee_name,
                'is_personal_work': is_personal_work,
                'work_type': '휴가/교육용 스프린트' if is_personal_work else '일반 업무 스프린트'
            })

        return {
            "status": "success",
            "message": f"현재 스프린트 이슈 {total_count}개 조회 완료",
            "data": {
                "issues": issues,
                "total_count": total_count,
                "personal_work_count": personal_work_count,
                "regular_work_count": regular_work_count,
                "current_user": current_user,
                "current_sprint": current_sprint,
                "jql_query": jql_query,
                "summary": {
                    "total_issues": total_count,
                    "personal_work_issues": personal_work_count,
                    "regular_work_issues": regular_work_count,
                    "has_personal_work": personal_work_count > 0,
                    "personal_work_ratio": round((personal_work_count / total_count * 100), 1) if total_count > 0 else 0,
                    "current_user": current_user,
                    "sprint_name": current_sprint.get('sprint_name', ''),
                    "sprint_start_date": current_sprint.get('start_date', '')
                }
            }
        }

    except requests.exceptions.RequestException as e:
        return {"status": "error", "message": f"JQL 쿼리 실행 실패: {e}"}
    except Exception as e:
        return {"status": "error", "message": f"예상치 못한 오류: {e}"}

@mcp.tool()
async def get_story_points(issue_key: str, issue_title: Optional[str] = None, explanation: str = None) -> Dict[str, Any]:
    """
    Jira 이슈의 Story Points(customfield_10002)를 조회합니다.

    Args:
        issue_key: Jira 이슈 키 (ex: "TVPLAT-677921")
        issue_title: 사용자 식별 편의를 위한 이슈 제목 (실제 기능에는 영향 없음, 함수 실행 시 자동 조회됨)

    Returns:
        Story Points 정보:
        - story_points: SP 값 (숫자)
        - expected_hours: 계획 시간 (SP * 4시간)
    """
    try:
        # 이슈 제목 조회
        issue_title = get_issue_title(issue_key)

        url = f"{JIRA_URL}rest/api/2/issue/{issue_key}?fields=customfield_10002"
        response = requests.get(url, auth=AUTH, timeout=10, verify=False)
        response.raise_for_status()

        data = response.json()
        story_points = data.get('fields', {}).get('customfield_10002')

        if story_points is None:
            return {
                "status": "warning",
                "message": f"이슈 {issue_key} ({issue_title})에 Story Points가 설정되지 않았습니다.",
                "data": {
                    "story_points": None,
                    "expected_hours": 0
                }
            }

        expected_hours = story_points * 4

        return {
            "status": "success",
            "message": f"이슈 {issue_key} ({issue_title})의 Story Points: {story_points}SP ({expected_hours}시간)",
            "data": {
                "story_points": story_points,
                "expected_hours": expected_hours
            }
        }

    except requests.exceptions.RequestException as e:
        return {"status": "error", "message": f"Story Points 조회 실패: {e}"}
    except Exception as e:
        return {"status": "error", "message": f"예상치 못한 오류: {e}"}

@mcp.tool()
async def edit_story_points(
    issue_key: str,
    new_story_points: float,
    issue_title: Optional[str] = None,
    explanation: str = None
) -> Dict[str, Any]:
    """
    Jira 이슈의 Story Points(customfield_10002)를 수정합니다.

    Args:
        issue_key: Jira 이슈 키 (ex: "TVPLAT-677921")
        new_story_points: 새로운 Story Points 값 (ex: 5.0, 8.0)
        issue_title: 사용자 식별 편의를 위한 이슈 제목 (실제 기능에는 영향 없음, 함수 실행 시 자동 조회됨)

    Returns:
        Story Points 수정 결과:
        - previous_story_points: 이전 SP 값
        - new_story_points: 새로운 SP 값
        - expected_hours: 새로운 계획 시간 (SP * 4시간)
        - changed: 실제 변경 여부

    Examples:
        - edit_story_points("TVPLAT-677921", 8.0)  # 8 SP로 변경
        - edit_story_points("TVPLAT-677921", 5.5)  # 5.5 SP로 변경
    """
    try:
        # 파라미터 검증
        if new_story_points < 0:
            return {
                "status": "error",
                "message": "Story Points는 0 이상의 값이어야 합니다."
            }

        # 이슈 제목 조회
        issue_title = get_issue_title(issue_key)

        # 1. 현재 Story Points 값 조회
        get_url = f"{JIRA_URL}rest/api/2/issue/{issue_key}?fields=customfield_10002"
        get_response = requests.get(get_url, auth=AUTH, timeout=10, verify=False)
        get_response.raise_for_status()

        get_data = get_response.json()
        previous_story_points = get_data.get('fields', {}).get('customfield_10002')

        # 2. 값 비교 및 변경 필요성 확인
        if previous_story_points == new_story_points:
            return {
                "status": "success",
                "message": f"이슈 {issue_key} ({issue_title})의 Story Points가 이미 {new_story_points}SP입니다.",
                "data": {
                    "previous_story_points": previous_story_points,
                    "new_story_points": new_story_points,
                    "expected_hours": new_story_points * 4,
                    "changed": False
                }
            }

        # 3. Story Points 업데이트
        update_url = f"{JIRA_URL}rest/api/2/issue/{issue_key}"
        payload = {
            "fields": {
                "customfield_10002": new_story_points
            }
        }

        update_response = requests.put(update_url, json=payload, auth=AUTH, timeout=10, verify=False)

        if not update_response.ok:
            error_detail = f"HTTP {update_response.status_code}: {update_response.text}"
            return {
                "status": "error",
                "message": f"Story Points 수정 실패: {error_detail}"
            }

        update_response.raise_for_status()
        expected_hours = new_story_points * 4

        return {
            "status": "success",
            "message": f"이슈 {issue_key} ({issue_title})의 Story Points를 {previous_story_points}SP에서 {new_story_points}SP로 변경했습니다. (계획 시간: {expected_hours}시간)",
            "data": {
                "previous_story_points": previous_story_points,
                "new_story_points": new_story_points,
                "expected_hours": expected_hours,
                "changed": True
            }
        }

    except requests.exceptions.RequestException as e:
        return {"status": "error", "message": f"Story Points 수정 실패: {e}"}
    except Exception as e:
        return {"status": "error", "message": f"예상치 못한 오류: {e}"}

@mcp.tool()
async def analyze_sprint(issue_key: str, issue_title: Optional[str] = None, explanation: str = None) -> Dict[str, Any]:
    """
    Jira 스프린트(이슈)(스토리, epic 타입 모두)를 종합 분석하여 worklog 해석오류와 Story Points 달성률을 검사합니다.

    분석 결과:
    1. Worklog 해석오류 검사 - LG전자 Work Description 양식 준수 여부
    2. Story Points 달성률 분석 - 계획 대비 실제 작업 시간 비교 (1SP = 4시간)

    Args:
        issue_key: Jira 이슈 키 (ex: "TVPLAT-677921")
        issue_title: 사용자 식별 편의를 위한 이슈 제목 (실제 기능에는 영향 없음, 함수 실행 시 자동 조회됨)

    Returns:
        종합 분석 결과:
        - story_points: 설정된 SP 값
        - expected_hours: 계획 시간 (SP * 4시간)
        - actual_hours: 실제 작업 시간 (worklog 합계)
        - shortage_hours: 부족 시간 (양수면 부족, 음수면 초과)
        - worklog_errors: 해석오류가 있는 worklog 목록
        - completion_rate: 달성률 (%)
        - sprint_period: 스프린트 기간 정보 (시작일, 종료일, 기간)
        - summary: 요약 정보

    Example:
        analyze_sprint("TVPLAT-677921")
        → 6SP 스프린트에서 18.5시간 작업, 5.5시간 부족, 해석오류 1건
    """
    try:
        # 1. 이슈 정보 조회 (Story Points + 제목)
        issue_url = f"{JIRA_URL}rest/api/2/issue/{issue_key}?fields=customfield_10002,summary"
        issue_response = requests.get(issue_url, auth=AUTH, timeout=10, verify=False)
        issue_response.raise_for_status()

        issue_data = issue_response.json()
        story_points = issue_data.get('fields', {}).get('customfield_10002')
        title = issue_data.get('fields', {}).get('summary', '')

        # 2. Worklog 조회
        wl_url = f"{JIRA_URL}rest/api/2/issue/{issue_key}/worklog"
        wl_response = requests.get(wl_url, auth=AUTH, timeout=10, verify=False)
        wl_response.raise_for_status()

        wl_data = wl_response.json()
        worklogs = wl_data.get('worklogs', [])

        # 3. Worklog 해석 오류 분석
        worklog_errors = analyze_worklog_errors(worklogs)

        # 4. 스프린트 기간 파싱 (제목에서)
        sprint_period = parse_sprint_period_from_title(title)
        work_days = calculate_work_days(worklogs)

        # 5. SP 메트릭 계산
        sp_metrics = calculate_sp_metrics(story_points, worklogs)

        # 6. 결과 종합
        return {
            "status": "success",
            "message": f"스프린트 {issue_key} ({title}) 분석 완료",
            "data": {
                "issue_key": issue_key,
                "story_points": sp_metrics['story_points'],
                "expected_hours": sp_metrics['expected_hours'],
                "actual_hours": sp_metrics['actual_hours'],
                "shortage_hours": sp_metrics['shortage_hours'],
                "completion_rate": sp_metrics['completion_rate'],
                "worklog_errors": worklog_errors,
                "worklog_count": len(worklogs),
                "sprint_period": sprint_period,
                "work_days": work_days,
                "summary": {
                    "has_story_points": story_points is not None,
                    "has_worklogs": len(worklogs) > 0,
                    "has_worklog_errors": len(worklog_errors) > 0,
                    "error_count": len(worklog_errors),
                    "shortage_hours": sp_metrics['shortage_hours'],
                    "completion_rate": sp_metrics['completion_rate'],
                    "duration_days": sprint_period['duration_days'],
                    "work_days": work_days,
                    "sprint_name": sprint_period['sprint_name'],
                    "status": "부족" if sp_metrics['shortage_hours'] > 0 else "달성" if sp_metrics['shortage_hours'] <= 0 else "미설정"
                }
            }
        }

    except requests.exceptions.RequestException as e:
        return {"status": "error", "message": f"스프린트 분석 실패: {e}"}
    except Exception as e:
        return {"status": "error", "message": f"예상치 못한 오류: {e}"}

def get_issue_status(issue_key: str) -> Dict[str, Any]:
    """이슈의 현재 상태와 제목을 조회합니다."""
    try:
        url = f"{JIRA_URL}rest/api/2/issue/{issue_key}?fields=status,summary"
        response = requests.get(url, auth=AUTH, timeout=10, verify=False)
        response.raise_for_status()

        data = response.json()
        fields = data.get('fields', {})
        status = fields.get('status', {})

        return {
            "success": True,
            "status_name": status.get('name', ''),
            "summary": fields.get('summary', ''),
            "error": None
        }
    except Exception as e:
        return {
            "success": False,
            "status_name": None,
            "summary": None,
            "error": str(e)
        }

def get_issue_transitions(issue_key: str) -> Dict[str, Any]:
    """이슈의 가능한 상태 전환 목록을 조회합니다."""
    try:
        url = f"{JIRA_URL}rest/api/2/issue/{issue_key}/transitions"
        response = requests.get(url, auth=AUTH, timeout=10, verify=False)
        response.raise_for_status()

        data = response.json()
        transitions_data = data.get('transitions', [])

        transitions = []
        for t in transitions_data:
            transitions.append({
                "id": t.get('id', ''),
                "name": t.get('name', ''),
                "to_status": t.get('to', {}).get('name', '')
            })

        return {
            "success": True,
            "transitions": transitions,
            "error": None
        }
    except Exception as e:
        return {
            "success": False,
            "transitions": [],
            "error": str(e)
        }

def execute_transition(issue_key: str, transition_id: str, comment: Optional[str] = None) -> Dict[str, Any]:
    """이슈의 상태 전환을 실행합니다."""
    try:
        url = f"{JIRA_URL}rest/api/2/issue/{issue_key}/transitions"

        payload = {
            "transition": {
                "id": transition_id
            }
        }

        if comment:
            payload["update"] = {
                "comment": [
                    {
                        "add": {
                            "body": comment
                        }
                    }
                ]
            }

        response = requests.post(url, json=payload, auth=AUTH, timeout=10, verify=False)
        response.raise_for_status()

        return {
            "success": True,
            "error": None
        }
    except Exception as e:
        return {
            "success": False,
            "error": str(e)
        }

@mcp.tool()
async def close_sprint_issue(
    issue_key: str,
    resolution: str = "Done",
    comment: Optional[str] = None,
    explanation: str = None
) -> Dict[str, Any]:
    """
    완성된 스프린트 이슈를 Workflow -> Resolve -> Close 프로세스에 따라 닫습니다.

    주요 기능:
    1. 이슈의 현재 상태 확인
    2. 가능한 상태 전환 목록 조회
    3. Close 상태로 가는 최적 경로 찾기 (직접 Close 또는 Resolve -> Close)
    4. 단계별 상태 전환 실행
    5. 전환 과정에서 코멘트 추가 (선택사항)

    Args:
        issue_key: 닫을 이슈의 키 (ex: "TVPLAT-700669")
        resolution: 해결 방법 (기본값: "Done", 다른 옵션: "Fixed", "Won't Do" 등)
        comment: 닫을 때 추가할 코멘트 (선택사항)

    Returns:
        이슈 닫기 결과:
        - success: 성공 여부
        - issue_key: 처리된 이슈 키
        - initial_status: 초기 상태
        - final_status: 최종 상태
        - transitions_performed: 수행된 전환 목록
        - message: 결과 메시지
        - error: 오류 메시지 (실패 시)

    Example:
        close_sprint_issue("TVPLAT-700669", comment="스프린트 완료로 인한 이슈 종료")
        → 이슈를 Resolve -> Close 순서로 전환하여 닫기

    Note:
        - 이미 닫힌 이슈는 처리하지 않습니다
        - 권한이 없거나 전환이 불가능한 경우 오류를 반환합니다
        - Jira 워크플로우 설정에 따라 전환 경로가 달라질 수 있습니다
    """
    try:
        # 1. 이슈 현재 상태 확인
        status_result = get_issue_status(issue_key)
        if not status_result["success"]:
            return {
                "success": False,
                "issue_key": issue_key,
                "error": f"이슈 상태 조회 실패: {status_result['error']}"
            }

        initial_status = status_result["status_name"]
        issue_summary = status_result["summary"]

        # 이미 닫힌 이슈 체크
        if initial_status.lower() in ['closed', 'done', 'resolved']:
            return {
                "success": True,
                "issue_key": issue_key,
                "initial_status": initial_status,
                "final_status": initial_status,
                "transitions_performed": [],
                "message": f"이슈 {issue_key} ({issue_summary})는 이미 '{initial_status}' 상태입니다."
            }

        # 2. 가능한 전환 목록 조회
        transitions_result = get_issue_transitions(issue_key)
        if not transitions_result["success"]:
            return {
                "success": False,
                "issue_key": issue_key,
                "error": f"전환 목록 조회 실패: {transitions_result['error']}"
            }

        transitions = transitions_result["transitions"]
        transitions_performed = []

        # 3. 자동으로 닫힌 상태까지 전환하기
        current_status = initial_status
        max_attempts = 10  # 무한 루프 방지
        attempt = 0

        while attempt < max_attempts:
            # 현재 상태가 이미 닫힌 상태인지 확인
            if current_status.lower() in ['closed', 'close', 'done', 'resolved']:
                final_status = current_status
                break

            # 현재 상태에서 가능한 전환 조회
            transitions_result = get_issue_transitions(issue_key)
            if not transitions_result["success"]:
                return {
                    "success": False,
                    "issue_key": issue_key,
                    "error": f"전환 목록 조회 실패: {transitions_result['error']}"
                }

            transitions = transitions_result["transitions"]
            if not transitions:
                break

            # 우선순위: Close > Done > Resolved > Verify > 기타
            next_transition = None
            priority_order = ['closed', 'close', 'done', 'resolved', 'resolve', 'verify']

            for priority in priority_order:
                for t in transitions:
                    if priority in t["to_status"].lower():
                        next_transition = t
                        break
                if next_transition:
                    break

            # 우선순위에 없으면 첫 번째 전환 사용
            if not next_transition and transitions:
                next_transition = transitions[0]

            if not next_transition:
                break

            # 전환 실행 (첫 번째 전환에만 comment 추가)
            use_comment = comment if attempt == 0 else None
            result = execute_transition(issue_key, next_transition["id"], use_comment)
            if not result["success"]:
                return {
                    "success": False,
                    "issue_key": issue_key,
                    "error": f"{current_status} -> {next_transition['to_status']} 전환 실패: {result['error']}"
                }

            # 전환 기록
            transitions_performed.append(f"{current_status} -> {next_transition['to_status']}")
            current_status = next_transition["to_status"]
            attempt += 1

            # 닫힌 상태에 도달했으면 종료
            if current_status.lower() in ['closed', 'close', 'done']:
                final_status = current_status
                break

        # 최대 시도 횟수 초과 또는 더 이상 전환할 수 없는 경우
        if attempt >= max_attempts:
            return {
                "success": False,
                "issue_key": issue_key,
                "error": f"최대 전환 시도 횟수({max_attempts})를 초과했습니다. 현재 상태: {current_status}"
            }

        if not transitions_performed:
            return {
                "success": False,
                "issue_key": issue_key,
                "error": f"이슈를 닫을 수 있는 전환 경로를 찾을 수 없습니다. 현재 상태: {current_status}"
            }

        final_status = current_status

        return {
            "success": True,
            "issue_key": issue_key,
            "initial_status": initial_status,
            "final_status": final_status,
            "transitions_performed": transitions_performed,
            "message": f"이슈 {issue_key} ({issue_summary})를 성공적으로 닫았습니다. 전환 경로: {' -> '.join(transitions_performed)}",
            "summary": {
                "issue_key": issue_key,
                "issue_title": issue_summary,
                "status_changed": initial_status != final_status,
                "transition_count": len(transitions_performed),
                "workflow_path": ' -> '.join(transitions_performed) if transitions_performed else 'No transitions needed'
            }
        }

    except requests.exceptions.RequestException as e:
        return {
            "success": False,
            "issue_key": issue_key,
            "error": f"네트워크 오류: {e}"
        }
    except Exception as e:
        return {
            "success": False,
            "issue_key": issue_key,
            "error": f"예상치 못한 오류: {e}"
        }

@mcp.tool()
async def copy_sprint_issue(
    source_issue_key: str,
    assignee: Optional[str] = None,
    offset: int = 1,
    explanation: str = None
) -> Dict[str, Any]:
    """
    기존 스프린트 이슈를 복사하여 다음 스프린트용 새로운 이슈를 생성합니다.

    주요 기능:
    1. 원본 이슈의 정보를 가져와서 복사
    2. 제목에서 스프린트 정보만 다음 스프린트로 업데이트
    3. Epic Link를 동일하게 유지하여 새로운 이슈 생성
    4. 담당자 지정 가능

    Args:
        source_issue_key: 복사할 원본 이슈 키 (ex: "TVPLAT-700666")
        assignee: 새 이슈의 담당자 (선택사항, 기본값: 현재 사용자)
        offset: 스프린트 오프셋 (기본값: 1 = 다음 스프린트)

    Returns:
        새로운 이슈 생성 결과:
        - status: 성공/실패 상태
        - message: 결과 메시지
        - data: 새 이슈 정보 (키, URL, 스프린트 정보 등)

    Example:
        copy_sprint_issue("TVPLAT-700666", assignee="jaehyung1.lee")
        → 기존 이슈를 복사하여 다음 스프린트용 새 이슈 생성
    """
    try:
        # 1. 원본 이슈 정보 조회
        issue_url = f"{JIRA_URL}rest/api/2/issue/{source_issue_key}?fields=summary,description,assignee,issuetype,priority,components,{EPIC_LINK_FIELD}"
        response = requests.get(issue_url, auth=AUTH, timeout=10, verify=False)
        response.raise_for_status()

        source_data = response.json()
        source_fields = source_data.get('fields', {})

        # 2. 다음 스프린트 정보 계산
        next_sprint = get_next_sprint_info(offset)

        # 3. 새로운 제목 생성 (스프린트 정보만 업데이트)
        original_summary = source_fields.get('summary', '')
        new_summary = update_sprint_in_title(original_summary, next_sprint['sprint_name'])

        # 4. 담당자 설정
        if not assignee:
            assignee = get_current_user()

        # 5. 새 이슈 필드 준비
        new_fields = {
            "project": {"key": "TVPLAT"},
            "summary": new_summary,
            "description": source_fields.get('description', ''),
            "issuetype": source_fields.get('issuetype', {"name": "Story"}),
            "priority": source_fields.get('priority', {"name": "P2"}),
            "assignee": {"name": assignee},
            "components": source_fields.get('components', [{"name": "_WorkLog"}]),
            "duedate": next_sprint['end_date'],
            EPIC_LINK_FIELD: source_fields.get(EPIC_LINK_FIELD, DEFAULT_EPIC_KEY),
            SPRINT_FIELD: next_sprint['sprint_id']
        }

        # 6. 새 이슈 생성
        create_result = create_jira_issue(new_fields)

        if not create_result["success"]:
            return {
                "status": "error",
                "message": f"이슈 생성 실패: {create_result['error']}",
                "data": None
            }

        return {
            "status": "success",
            "message": f"이슈 {source_issue_key}를 복사하여 새로운 이슈 {create_result['issue_key']}를 생성했습니다.",
            "data": {
                "source_issue_key": source_issue_key,
                "new_issue_key": create_result['issue_key'],
                "new_issue_url": create_result['issue_url'],
                "sprint_info": next_sprint['sprint_name'],
                "assignee": assignee,
                "new_summary": new_summary,
                "epic_link": new_fields[EPIC_LINK_FIELD]
            }
        }

    except requests.exceptions.RequestException as e:
        return {
            "status": "error",
            "message": f"원본 이슈 조회 실패: {e}",
            "data": None
        }
    except Exception as e:
        return {
            "status": "error",
            "message": f"예상치 못한 오류: {e}",
            "data": None
        }

@mcp.tool()
async def create_new_sprint_issue(
    assignee: str,
    task_type: str,
    description: Optional[str] = None,
    offset: int = 1,
    explanation: str = None
) -> Dict[str, Any]:
    """
    신규로 다음 스프린트용 이슈를 생성합니다.

    주요 기능:
    1. 완전히 새로운 이슈 생성
    2. 다음 스프린트 정보로 제목 생성
    3. Epic Link를 AIS Planned 업무 Epic에 연결
    4. 담당자와 업무 유형 지정

    Args:
        assignee: 담당자 이름 (필수, ex: "jaehyung1.lee")
        task_type: 업무 유형 (필수, ex: "기타업무", "스토리북 개발", "AIDD 과제")
        description: 업무 설명 (선택사항)
        offset: 스프린트 오프셋 (기본값: 1 = 다음 스프린트)

    Returns:
        새로운 이슈 생성 결과:
        - status: 성공/실패 상태
        - message: 결과 메시지
        - data: 새 이슈 정보 (키, URL, 스프린트 정보 등)

    Example:
        create_new_sprint_issue("jaehyung1.lee", "기타업무", "운영, 회의, 교육, 파트 업무")
        → "이재형 / 기타업무" 제목의 다음 스프린트 이슈 생성
    """
    try:
        # 1. 다음 스프린트 정보 계산
        next_sprint = get_next_sprint_info(offset)

        # 2. 담당자 이름 추출 (이메일에서 이름 부분만)
        display_name = assignee.split('.')[0] if '.' in assignee else assignee

        # 3. 제목 생성: [2025_IR3SP24(8/11-8/22)] 이재형 / 기타업무
        summary = f"[{next_sprint['sprint_name']}] {display_name} / {task_type}"

        # 4. 새 이슈 필드 준비
        new_fields = {
            "project": {"key": "TVPLAT"},
            "summary": summary,
            "description": description or f"{task_type} 관련 업무",
            "issuetype": {"name": "Story"},
            "priority": {"name": "P2"},
            "assignee": {"name": assignee},
            "components": [{"name": "_WorkLog"}],
            "duedate": next_sprint['end_date'],
            EPIC_LINK_FIELD: DEFAULT_EPIC_KEY,
            SPRINT_FIELD: next_sprint['sprint_id']
        }

        # 5. 새 이슈 생성
        create_result = create_jira_issue(new_fields)

        if not create_result["success"]:
            return {
                "status": "error",
                "message": f"이슈 생성 실패: {create_result['error']}",
                "data": None
            }

        return {
            "status": "success",
            "message": f"새로운 스프린트 이슈 {create_result['issue_key']}를 생성했습니다.",
            "data": {
                "new_issue_key": create_result['issue_key'],
                "new_issue_url": create_result['issue_url'],
                "sprint_info": next_sprint['sprint_name'],
                "assignee": assignee,
                "task_type": task_type,
                "summary": summary,
                "description": new_fields["description"],
                "epic_link": DEFAULT_EPIC_KEY
            }
        }

    except Exception as e:
        return {
            "status": "error",
            "message": f"예상치 못한 오류: {e}",
            "data": None
        }

if __name__ == "__main__":
    print("Starting Sprint Analyzer MCP server with STDIO transport...")
    mcp.run()  # STDIO transport (default)