"""
보고서 생성 서비스

이 모듈은 VOC 데이터를 기반으로 HWP 및 PDF 보고서를 생성하고
프린터 출력 기능을 제공합니다. 복잡한 VOC 파싱, 열차 정보 분석,
입출고 추적 로직은 별도 유틸리티로 분리되어 있습니다.

주요 기능:
    - HWP 보고서 생성: 한글 템플릿 기반 자동 작성
    - PDF 보고서 생성: ReportLab 기반 PDF 출력
    - 프린터 인쇄: Windows 기본 프린터로 직접 출력
    - 기존 파일 체크: 중복 생성 방지 및 선택 옵션 제공
    - 자동 정보 추출: VOC 내용에서 호선, 편성, 열차번호 등 파싱
    - 입고 정보 생성: 시각표 기반 입고지 및 시간 자동 계산

리팩토링 히스토리 (v2.0):
    기존 1056줄의 단일 클래스를 다음과 같이 분리:
    
    1. VOCParser (utils/voc_parser.py)
       - 텍스트 파싱 및 정보 추출
       - 정규식 패턴 매칭
       - 제목 정제 및 포맷팅
    
    2. DateScheduleUtils (utils/date_utils.py)
       - 날짜 파싱 및 요일 변환
       - 공휴일 판별
       - 스케줄 타입 분류
    
    3. TrainAnalyzer (utils/train_analyzer.py)
       - 열차 종별/방향 판별
       - 입출고 추적
       - 주박지 로직
    
    4. ReportService (현재 파일, ~400줄)
       - 보고서 생성 핵심 로직
       - 유틸리티 통합 관리
       - HWP/PDF 출력

개선 효과:
    - 코드 라인 수: 1056줄 → 400줄 (62% 감소)
    - 단일 책임 원칙 준수
    - 재사용성 향상 (다른 서비스에서도 유틸리티 사용 가능)
    - 테스트 용이성 증가 (각 모듈 독립 테스트)
    - 유지보수성 개선 (버그 수정 시 해당 모듈만 수정)

HWP 보고서 생성 프로세스:
    1. VOC 데이터 수신 (제목, 내용, 날짜 등)
    2. VOCParser로 기본 정보 추출
    3. DateScheduleUtils로 날짜 타입 판별
    4. TrainAnalyzer로 열차 정보 분석
    5. TimetableService로 입고 정보 조회
    6. 템플릿 파일 열기 (VOC_Sample.hwp)
    7. 필드에 데이터 입력
    8. 파일명 생성 및 저장
    9. 기존 파일 체크 및 처리

보안 모듈:
    FilePathCheckerModule.dll을 레지스트리에 등록하여
    HWP 파일 접근 시 보안 경고창을 억제합니다.

의존성:
    - pywin32: HWP 자동화 및 프린터 제어
    - reportlab: PDF 생성
    - VOCParser: VOC 텍스트 파싱
    - TrainAnalyzer: 열차 정보 분석
    - DateScheduleUtils: 날짜 처리
    - TimetableService: 시각표 조회

사용 예시:
    >>> from services.report_service import ReportService
    >>> 
    >>> service = ReportService()
    >>> 
    >>> # VOC 데이터
    >>> voc_data = {
    ...     'id': '12345',
    ...     'title': '1호선 서면역 소음 발생',
    ...     'content': '2월 17일 17:34 서면역에서...',
    ...     'date': '2026-02-17',
    ...     'department': '차량',
    ...     'status': '접수',
    ...     # 사용자 입력 (옵션)
    ...     'office': '신평차량사업소',
    ...     'team': '검수1',
    ...     'reporter_name': '이대석'
    ... }
    >>> 
    >>> # HWP 보고서 생성
    >>> success, message = service.create_hwp_report(voc_data)
    >>> if success:
    ...     print(f"보고서 생성 완료: {message}")
    ... elif success == "FILE_EXISTS":
    ...     print(f"기존 파일 존재: {message}")
    >>> 
    >>> # PDF 보고서 생성
    >>> success, message = service.create_pdf_report(voc_data)

설정 파일 (settings.json):
    {
        "report": {
            "output_path": "D:/Reports"  // 보고서 저장 경로
        },
        "master_data": {
            "stations_L1": ["서면", "부산역", ...]  // 역명 리스트
        },
        "report_options": {
            "office": "신평차량사업소",
            "team": "검수1",
            ...
        }
    }

작성자: KH.Choi
최종 수정: 2026-02-17
버전: 2.0 (대규모 리팩토링)
"""
import os
import re
import winreg
from pathlib import Path
from datetime import datetime
import json

# Windows API 모듈
try:
    import win32print
    import win32ui
    import win32con
    import win32com.client
    HAS_WIN32 = True
except ImportError:
    HAS_WIN32 = False

# ReportLab (PDF)
try:
    from reportlab.pdfgen import canvas
    from reportlab.lib.pagesizes import A4
    from reportlab.pdfbase import pdfmetrics
    from reportlab.pdfbase.ttfonts import TTFont
    HAS_REPORTLAB = True
except ImportError:
    HAS_REPORTLAB = False

from utils.path_utils import get_base_dir
from utils.logger import get_logger
from utils.voc_parser import VOCParser
from utils.train_analyzer import TrainAnalyzer
from utils.date_utils import DateScheduleUtils
from services.timetable_service import TimetableService

BASE_DIR = get_base_dir()
CONFIG_FILE = BASE_DIR / "data" / "settings.json"


class ReportService:
    """
    보고서 생성 및 인쇄 서비스
    
    VOC 데이터를 분석하여 HWP/PDF 보고서를 생성하고 프린터 출력 기능을 제공합니다.
    복잡한 파싱 및 분석 로직은 별도 유틸리티 클래스로 위임하여 처리합니다.
    
    Attributes:
        logger: 로거 인스턴스
        config (dict): settings.json에서 로드한 설정
        parser (VOCParser): VOC 텍스트 파서
        timetable (TimetableService): 시각표 서비스
        train_analyzer (TrainAnalyzer): 열차 분석기
        default_settings (dict): 기본 보고서 설정
    
    주요 메서드:
        create_hwp_report: HWP 보고서 생성
        create_pdf_report: PDF 보고서 생성
        print_voc_detail: 프린터 출력
        _parse_voc_info: VOC 정보 파싱 (통합 메서드)
    
    아키텍처 개선:
        기존: ReportService가 모든 로직 처리 (1056줄)
        현재: 역할별로 분리된 유틸리티 활용 (400줄)
        
        ReportService
        ├── VOCParser (텍스트 파싱)
        ├── DateScheduleUtils (날짜 처리)
        ├── TrainAnalyzer (열차 분석)
        └── TimetableService (시각표 조회)
    
    보고서 생성 흐름:
        1. VOC 데이터 수신
        2. _parse_voc_info()로 정보 추출
           ├─ VOCParser: 기본 정보 파싱
           ├─ DateScheduleUtils: 날짜 타입 판별
           ├─ TimetableService: 열차 후보 검색
           └─ TrainAnalyzer: 열차 상세 분석
        3. TrainAnalyzer로 입고 정보 생성
        4. HWP 템플릿에 데이터 입력
        5. 파일 저장 (중복 체크)
    
    기존 파일 처리:
        동일한 보고서가 이미 존재하는 경우:
        - 반환값: ("FILE_EXISTS", 파일경로)
        - Controller에서 사용자 선택 다이얼로그 표시
        - 옵션: 기존 파일 열기 / 새로 생성 / 취소
    
    사용 시나리오:
        1. VOC 상세 화면에서 "보고서 생성" 버튼 클릭
        2. ReportOptionDialog에서 사업소, 팀 등 선택
        3. Controller가 ReportService.create_hwp_report() 호출
        4. 보고서 생성 완료 후 ReportCompleteDialog 표시
    
    예시:
        >>> service = ReportService()
        >>> 
        >>> # 설정 로드 확인
        >>> print(service.config.get('report', {}).get('output_path'))
        D:/Reports
        >>> 
        >>> # 보고서 생성
        >>> result = service.create_hwp_report(voc_data)
        >>> if result[0] == "FILE_EXISTS":
        ...     print(f"기존 파일: {result[1]}")
        ... elif result[0]:
        ...     print("생성 성공")
    """
    
    def __init__(self):
        """초기화"""
        self.logger = get_logger("ReportService")
        self.config = self._load_config()
        
        # 역명 리스트
        stations = self.config.get("master_data", {}).get("stations_L1", [])
        
        # 유틸리티 초기화
        self.parser = VOCParser(stations)
        self.timetable = TimetableService()
        self.train_analyzer = TrainAnalyzer(self.timetable, self.logger)
        
        # 기본 설정
        self.default_settings = {
            "dept_office": "신평차량사업소",
            "dept_team": "검수1",
            "reporter_name": "이대석",
            "reporter_pos": "팀장",
            "reporter_tel": "200-5144",
            "action_taken": "○ 해당 편성 점검 완료"
        }
    
    def _load_config(self):
        """설정 파일 로드"""
        try:
            if CONFIG_FILE.exists():
                with open(CONFIG_FILE, 'r', encoding='utf-8') as f:
                    return json.load(f)
        except Exception:
            pass
        return {}
    
    def get_line_spec(self, line_str):
        """
        호선별 제원 정보 반환
        
        Args:
            line_str (str): 호선 문자열 (예: "1호선", "2호선")
            
        Returns:
            dict: {"max_set": 최대편성수, "max_car": 최대호차수}
        
        Examples:
            >>> service.get_line_spec("1호선")
            {'max_set': 51, 'max_car': 8}
            >>> service.get_line_spec("")
            {'max_set': 56, 'max_car': 8}  # default
        """
        return TrainAnalyzer.get_line_spec(line_str)
    
    def _setup_security_registry(self, dll_path):
        """HWP 보안 모듈 레지스트리 등록"""
        try:
            reg_path = r"Software\HNC\HwpAutomation\Modules"
            key = winreg.CreateKey(winreg.HKEY_CURRENT_USER, reg_path)
            module_name = "FilePathCheckDLL"
            winreg.SetValueEx(key, module_name, 0, winreg.REG_SZ, str(dll_path))
            winreg.CloseKey(key)
            return module_name
        except Exception as e:
            self.logger.warning(f"보안 모듈 등록 실패: {e}")
            return None
    
    def _parse_voc_info(self, title, content, doc_date):
        """
        VOC 정보 파싱 (통합 메서드)
        
        Args:
            title (str): 제목
            content (str): 본문
            doc_date (str): 문서 날짜
            
        Returns:
            dict: 파싱된 정보
        """
        full_text = f"{title} {content}"
        
        # 1. 기본 정보 추출
        info = self.parser.parse_basic_info(full_text)
        
        # 2. 날짜 및 스케줄 타입
        extracted_date, diagram_type = DateScheduleUtils.parse_schedule_type(full_text, doc_date)
        
        # 3. 문맥 기반 유추
        inf_line, inf_dir, inf_station = self.parser.infer_line_and_direction(full_text)
        
        if not info["line"]:
            info["line"] = inf_line
        
        # 4. 시간 추출
        found_time = self.parser.extract_time(full_text)
        
        # 5. 열차번호 재검색
        if not info["train"]:
            info["train"] = self.parser.search_train_number_in_context(full_text)
        
        # 6. 시각표 검증
        if info["train"] and self.timetable.is_loaded:
            if not self.timetable.train_exists(info["train"], diagram_type):
                self.logger.info(f"열차번호 시각표 미존재로 무시: {info['train']}")
                info["train"] = ""
        
        # 7. 후보군 검색
        candidates = []
        if inf_station and found_time:
            if not info["train"] and inf_dir:
                match = self.timetable.find_train_by_time(inf_station, inf_dir, found_time, diagram_type=diagram_type)
                if match:
                    info["train"] = f"{match['train_number']}"
                    self.logger.info(f"열차번호 유추 성공: {info['train']}")
            
            if not info["train"]:
                candidates = self.timetable.get_trains_in_window(
                    station=inf_station,
                    time_str=found_time,
                    window_minutes=25,
                    diagram_type=diagram_type,
                    direction=None
                )
                self.logger.info(f"열차 후보군 발견: {len(candidates)}개")
                
                # 본문에서 후보군 번호 찾기
                for cand in candidates:
                    cand_num = str(cand['train_number'])
                    idx = full_text.find(cand_num)
                    if idx == -1:
                        continue
                    before = full_text[max(0, idx - 12):idx]
                    if re.search(r"\d{2,4}-\d{2,4}-$", before):
                        continue
                    info["train"] = cand_num
                    self.logger.info(f"후보군 중 본문 일치: {cand_num}")
                    break
        
        # 8. 열차 상세 정보
        train_details = TrainAnalyzer.classify_train_details(info["train"], inf_dir)
        
        # 9. 제목 정제
        pure_title = self.parser.sanitize_title_for_filename(title)
        
        return {
            "line_str": info["line"],
            "set_str": info["set"],
            "car_str": info["car"],
            "train_str": info["train"],
            "pure_title": pure_title,
            "train_details": train_details,
            "diagram_type": diagram_type,
            "train_candidates": candidates,
            "extracted_date": extracted_date
        }
    
    def print_voc_detail(self, data):
        """VOC 상세 내용 인쇄"""
        if not HAS_WIN32:
            return False, "윈도우 인쇄 모듈(pywin32)이 로드되지 않았습니다.\n(pip install pywin32)"
        
        try:
            hDC = win32ui.CreateDC()
            hDC.CreatePrinterDC(win32print.GetDefaultPrinter())
            hDC.StartDoc(f"VOC_{data['id']}")
            hDC.StartPage()
            
            y = 100
            line_height = 80
            
            hDC.TextOut(100, y, f"제목: {data['title']}")
            y += line_height * 2
            
            info_txt = f"접수번호: {data['id']} | 부서: {data.get('department')} | 작성자: {data.get('writer')}"
            hDC.TextOut(100, y, info_txt)
            y += line_height * 2
            
            content = str(data.get('content') or '')
            hDC.TextOut(100, y, "<질의 내용>")
            y += line_height
            
            max_chars = 60
            for i in range(0, len(content), max_chars):
                line = content[i:i+max_chars]
                hDC.TextOut(100, y, line)
                y += line_height
                if y > 4000:
                    break
            
            y += line_height
            hDC.TextOut(100, y, "<답변 내용>")
            y += line_height
            answer = str(data.get('answer') or '')
            for i in range(0, len(answer), max_chars):
                line = answer[i:i+max_chars]
                hDC.TextOut(100, y, line)
                y += line_height
                if y > 6500:
                    break
            
            hDC.EndPage()
            hDC.EndDoc()
            hDC.DeleteDC()
            return True, "인쇄가 완료되었습니다."
        
        except Exception as e:
            return False, f"인쇄 오류: {e}"
    
    def create_hwp_report(self, data):
        """HWP 보고서 생성"""
        if not HAS_WIN32:
            return False, "윈도우 자동화 모듈(pywin32)이 로드되지 않았습니다."
        
        try:
            # 1. 한글 실행
            try:
                hwp = win32com.client.gencache.EnsureDispatch("HWPFrame.HwpObject")
            except:
                hwp = win32com.client.Dispatch("HWPFrame.HwpObject")
            
            # 2. 보안 모듈 등록
            dll_path = BASE_DIR / "assets" / "FilePathCheckerModule.dll"
            if dll_path.exists():
                reg_module_name = self._setup_security_registry(dll_path)
                if reg_module_name:
                    res = hwp.RegisterModule("FilePathCheckDLL", reg_module_name)
                    if not res:
                        self.logger.warning(f"보안 모듈 적용 실패")
            
            hwp.XHwpWindows.Item(0).Visible = True
        except Exception as e:
            self.logger.error(f"한글 실행 실패: {e}")
            return False, f"한글(Hwp) 실행 실패: {e}"
        
        try:
            # 3. 템플릿 열기
            sample_file = BASE_DIR / "assets" / "VOC_Sample.hwp"
            if not sample_file.exists():
                return False, f"템플릿 파일을 찾을 수 없습니다.\n{sample_file}"
            
            hwp.Open(str(sample_file))
            
            # 4. 데이터 파싱
            raw_title = data.get('title', '')
            raw_content = data.get('content', '')
            doc_date = str(data.get('date', '')).replace('-', '.')
            
            formatted_content = self.parser.format_voc_content(raw_title, raw_content)
            parsed = self._parse_voc_info(raw_title, raw_content, doc_date)
            
            # 5. UI 값 우선 적용
            line_str_ui = data.get('line_str') or parsed['line_str']
            set_str_raw = data.get('train_set') or parsed['set_str']
            car_str_raw = data.get('car_str') or parsed['car_str']
            train_num_raw = str(data.get('train_num', '')).strip() or parsed['train_str']
            
            # 열번 검증: CTkComboBox 같은 위젯 이름 필터링
            if train_num_raw and (train_num_raw.startswith("CTk") or "ComboBox" in train_num_raw):
                train_num = ""  # 잘못된 값은 빈 문자열로
            else:
                train_num = train_num_raw
            
            # 접미사 복구
            set_str = set_str_raw
            if set_str and "편성" not in set_str:
                set_str += "편성"
            car_str = car_str_raw
            if car_str and "호차" not in car_str:
                car_str += "호차"
            
            # 6. 열차 상세 정보 재계산
            train_details = parsed['train_details']
            if train_num:
                context_dir = parsed['train_details'].get('direction', '')
                train_details = TrainAnalyzer.classify_train_details(train_num, context_dir)
            
            # 7. 비고 생성
            target_date_obj = parsed.get('extracted_date')
            if isinstance(target_date_obj, datetime):
                target_date_obj = target_date_obj.date()
            
            remarks_text = self.train_analyzer.get_schedule_remarks(
                train_details,
                diagram_type=parsed.get("diagram_type"),
                candidates=parsed.get("train_candidates"),
                current_date=target_date_obj
            )
            
            # 8. 필드 데이터 준비
            line_str_file = line_str_ui.replace("\n", "").replace("\r", "")
            line_str_hwp = "\r\n".join(line_str_file)
            
            dept_office = data.get('office', self.default_settings['dept_office'])
            dept_team = data.get('team', self.default_settings['dept_team'])
            reporter_name = data.get('reporter_name', self.default_settings['reporter_name'])
            reporter_pos = data.get('position', self.default_settings['reporter_pos'])
            reporter_tel = data.get('reporter_tel', self.default_settings['reporter_tel'])
            
            action_taken = data.get('action_taken', self.default_settings['action_taken'])
            if action_taken:
                # 열번이 있을 때만 열번 포함
                if train_num:
                    action_taken = f"▷ {train_num}열차 {set_str} {car_str}\r\n - {action_taken}"
                else:
                    # 열번이 없을 때는 편성과 호차만 표시
                    action_taken = f"▷ {set_str} {car_str}\r\n - {action_taken}"
            
            field_data = {
                "doc_date": doc_date,
                "doc_day": DateScheduleUtils.get_korean_weekday(doc_date),
                "dept_office": dept_office,
                "dept_team": dept_team,
                "reporter_name": reporter_name,
                "reporter_pos": reporter_pos,
                "reporter_tel": reporter_tel,
                "voc_content": formatted_content,
                "action_taken": action_taken,
                "remarks": remarks_text,
                "line_num": line_str_hwp,
                "train_set": set_str,
                "train_num": train_num,
                "train_type": train_details.get('type', ''),
                "train_dir": train_details.get('direction', '')
            }
            
            # 9. 필드 입력
            for field, value in field_data.items():
                hwp.PutFieldText(field, value)
            
            # 10. 파일명 생성 및 저장
            safe_title = re.sub(r'[\\/:*?"<>|\r\n]', "", parsed['pure_title']).strip()
            date_clean = doc_date.replace(".", "")
            f_train = f"{train_num}열차" if train_num else ""
            base_name = f"{line_str_file} {f_train} {set_str} {car_str} {safe_title} 관련".strip()
            base_name = re.sub(r'\s+', ' ', base_name)
            final_filename = f"{base_name}({date_clean})(VOC).hwp"
            
            # 저장 경로
            output_dir = self.config.get('report', {}).get('output_path', '')
            if not output_dir or not Path(output_dir).exists():
                output_dir = BASE_DIR.parent / "output"
            else:
                output_dir = Path(output_dir)
            
            save_path = output_dir / final_filename
            
            # 기존 파일 체크
            if save_path.exists():
                return "FILE_EXISTS", str(save_path)
            
            if not save_path.parent.exists():
                save_path.parent.mkdir(parents=True, exist_ok=True)
            
            hwp.SaveAs(str(save_path))
            return True, f"HWP 보고서가 생성되었습니다.\n{save_path}"
        
        except Exception as e:
            self.logger.error(f"HWP 생성 오류: {e}")
            return False, f"보고서 생성 중 오류가 발생했습니다.\n{e}"
    
    def create_pdf_report(self, data):
        """PDF 보고서 생성"""
        if not HAS_REPORTLAB:
            return False, "PDF 생성 모듈(reportlab)이 설치되지 않았습니다.\n(pip install reportlab)"
        
        try:
            date_str = str(data.get('date') or '').replace('-', '')[:8]
            if not date_str:
                date_str = datetime.now().strftime("%Y%m%d")
            
            safe_title = "".join([c for c in data['title'] if c.isalnum() or c in (' ', '(', ')', '[', ']')]).strip()
            filename = f"{safe_title}({date_str})(VOC).pdf"
            
            # 저장 경로
            output_dir = self.config.get('report', {}).get('output_path', '')
            if not output_dir or not Path(output_dir).exists():
                output_dir = BASE_DIR.parent / "output"
            else:
                output_dir = Path(output_dir)
            
            if not output_dir.exists():
                output_dir.mkdir(parents=True, exist_ok=True)
            
            full_path = output_dir / filename
            
            # 폰트 등록
            font_path = "C:/Windows/Fonts/malgun.ttf"
            if not os.path.exists(font_path):
                return False, "한글 폰트(malgun.ttf)를 찾을 수 없습니다."
            
            pdfmetrics.registerFont(TTFont('Malgun', font_path))
            
            c = canvas.Canvas(str(full_path), pagesize=A4)
            width, height = A4
            y = height - 50
            margin = 50
            line_gap = 20
            
            c.setFont("Malgun", 16)
            c.drawString(margin, y, f"제목: {data['title']}")
            y -= line_gap * 2
            
            c.setFont("Malgun", 10)
            c.drawString(margin, y, f"접수번호: {data['id']}  |  등록일자: {data.get('date')}  |  부서: {data.get('department')}")
            y -= line_gap
            c.drawString(margin, y, f"작성자: {data.get('writer')}  |  상태: {data['status']}")
            y -= line_gap * 2
            
            def draw_multiline_text(text, x, cur_y, max_w):
                c.setFont("Malgun", 10)
                lines = []
                chars_per_line = 45
                for i in range(0, len(text), chars_per_line):
                    lines.append(text[i:i+chars_per_line])
                
                for line in lines:
                    if cur_y < 50:
                        c.showPage()
                        c.setFont("Malgun", 10)
                        cur_y = height - 50
                    c.drawString(x, cur_y, line)
                    cur_y -= line_gap
                return cur_y
            
            c.setFont("Malgun", 12)
            c.drawString(margin, y, "[질의 내용]")
            y -= line_gap
            
            content = str(data.get('content') or '')
            y = draw_multiline_text(content, margin, y, width - 2*margin)
            y -= line_gap
            
            c.setFont("Malgun", 12)
            c.drawString(margin, y, "[답변 내용]")
            y -= line_gap
            
            answer = str(data.get('answer') or '')
            y = draw_multiline_text(answer, margin, y, width - 2*margin)
            
            c.save()
            return True, f"PDF 보고서가 생성되었습니다.\n{full_path}"
        
        except Exception as e:
            self.logger.error(f"PDF 생성 오류: {e}")
            return False, f"PDF 생성 오류: {e}"

    def get_line_info(self, line_str: str) -> dict:
        """
        호선별 역/방향 정보 반환 (UI 연동용)
        
        Args:
            line_str (str): 호선명 (예: "1호선")
            
        Returns:
            dict: {"stations": [...], "directions": [...]}
        """
        # 기본값
        info = {
            "stations": [],
            "directions": ["상행", "하행"]
        }
        
        if not line_str:
            return info
            
        if "1호선" in line_str:
            # 1호선 역 정보는 config 또는 parser에서 가져옴
            if self.parser and hasattr(self.parser, 'stations'):
                info["stations"] = self.parser.stations
            else:
                info["stations"] = self.config.get("master_data", {}).get("stations_L1", [])
                
            info["directions"] = ["신평/다대포행(하선)", "노포행(상선)"]
            
        elif "2호선" in line_str:
            info["directions"] = ["장산행(상선)", "양산행(하선)"]
        elif "3호선" in line_str:
            info["directions"] = ["수영행(상선)", "대저행(하선)"]
        elif "4호선" in line_str:
            info["directions"] = ["미남행(상선)", "안평행(하선)"]
            
        return info
