"""
VOC 통계 분석 서비스

VOC 데이터를 분석하여 통계 보고서를 생성하는 서비스입니다.
데이터 집계, 차트 생성, Excel/PDF 보고서 생성을 담당합니다.

주요 기능:
    - 통계 데이터 집계 (기간별, 부서별, 상태별)
    - 키워드 빈도 분석
    - 차트 생성 (matplotlib)
    - Excel 보고서 생성
    - PDF 보고서 생성 (선택)

작성자: KH.Choi
최종 수정: 2026-02-18
버전: 1.0
"""
from datetime import datetime
from pathlib import Path
from typing import Tuple, Optional
from collections import Counter

from models.statistics import StatisticsReport, StatisticsOptions
from utils.database import VOCDatabase
from utils.chart_generator import ChartGenerator
from utils.logger import get_logger
from utils.path_utils import get_data_dir
from core.exceptions import ReportError, DatabaseError


class StatisticsService:
    """
    VOC 통계 분석 서비스
    
    VOC 데이터를 분석하여 통계 보고서를 생성합니다.
    Database에서 데이터를 집계하고, ChartGenerator로 차트를 생성하며,
    최종적으로 Excel 또는 PDF 보고서를 생성합니다.
    
    Attributes:
        logger: 로거 인스턴스
        db (VOCDatabase): 데이터베이스 인스턴스
        chart_generator (ChartGenerator): 차트 생성기
    
    주요 메서드:
        generate_report: 통계 보고서 생성 (전체 프로세스)
        get_stats_summary: 통계 요약 조회
        export_to_excel: Excel 파일 생성
        export_to_pdf: PDF 파일 생성
    
    사용 예시:
        >>> service = StatisticsService(db)
        >>> options = StatisticsOptions(
        ...     period_start="2026-01-01",
        ...     period_end="2026-01-31",
        ...     output_format="excel"
        ... )
        >>> success, message = service.generate_report(options)
    """
    
    def __init__(self, db: VOCDatabase):
        """
        통계 서비스 초기화
        
        Args:
            db: VOCDatabase 인스턴스
        """
        self.logger = get_logger("StatisticsService")
        self.db = db
        self.chart_generator = ChartGenerator()
        
        # 통계 인덱스 생성 (성능 최적화)
        self.db.create_statistics_indexes()
    
    def generate_report(self, options: StatisticsOptions) -> Tuple[bool, str]:
        """
        통계 보고서 생성 (전체 프로세스)
        
        1. 데이터 집계
        2. 차트 생성
        3. 보고서 파일 생성 (Excel/PDF)
        
        Args:
            options: 통계 분석 옵션
            
        Returns:
            tuple[bool, str]: (성공 여부, 메시지 또는 파일 경로)
        """
        try:
            self.logger.info(f"통계 보고서 생성 시작: {options.period_start} ~ {options.period_end}")
            
            # 1. 통계 데이터 집계
            report = self.get_stats_summary(options.period_start, options.period_end)
            
            # 데이터 없음 체크
            if report.total_count == 0:
                return (False, "선택한 기간에 VOC 데이터가 없습니다.")
            
            # 2. 차트 생성 (옵션에 따라)
            if options.include_charts:
                try:
                    self._generate_charts(report, options)
                except Exception as e:
                    self.logger.warning(f"차트 생성 실패 (계속 진행): {e}")
            
            # 3. 보고서 파일 생성
            output_path = options.output_path or str(get_data_dir() / "reports")
            Path(output_path).mkdir(parents=True, exist_ok=True)
            
            if options.output_format in ("excel", "both"):
                excel_success, excel_path = self.export_to_excel(report, output_path)
                if not excel_success:
                    return (False, excel_path)
            
            if options.output_format in ("pdf", "both"):
                pdf_success, pdf_path = self.export_to_pdf(report, output_path)
                if not pdf_success:
                    return (False, pdf_path)
            
            # 성공 메시지
            if options.output_format == "both":
                message = f"보고서 생성 완료\nExcel: {excel_path}\nPDF: {pdf_path}"
            elif options.output_format == "excel":
                message = f"보고서 생성 완료\n{excel_path}"
            else:
                message = f"보고서 생성 완료\n{pdf_path}"
            
            self.logger.info("통계 보고서 생성 완료")
            return (True, message)
        
        except DatabaseError as e:
            self.logger.error(f"DB 조회 실패: {e.message}")
            return (False, f"데이터베이스 오류: {e.message}")
        except Exception as e:
            self.logger.error(f"보고서 생성 실패: {e}", exc_info=True)
            return (False, f"보고서 생성 중 오류 발생: {e}")
    
    def get_stats_summary(self, start_date: str, end_date: str) -> StatisticsReport:
        """
        통계 요약 조회
        
        Args:
            start_date: 시작일 (YYYY-MM-DD)
            end_date: 종료일 (YYYY-MM-DD)
            
        Returns:
            StatisticsReport: 통계 보고서 데이터
        """
        try:
            # 기간별 통계
            period_stats = self.db.get_stats_by_period(start_date, end_date)
            by_date = {item["day"]: item["count"] for item in period_stats}
            total_count = sum(by_date.values())
            
            # 부서별 통계
            dept_stats = self.db.get_stats_by_department(start_date, end_date)
            by_department = {item["department"]: item["count"] for item in dept_stats}
            
            # 상태별 통계
            status_stats = self.db.get_stats_by_status(start_date, end_date)
            by_status = {item["status"]: item["count"] for item in status_stats}
            
            # 키워드 분석
            top_keywords = self._analyze_keywords(start_date, end_date)
            
            # StatisticsReport 생성
            report = StatisticsReport(
                period_start=start_date,
                period_end=end_date,
                total_count=total_count,
                by_department=by_department,
                by_status=by_status,
                by_date=by_date,
                top_keywords=top_keywords,
                generated_at=datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            )
            
            return report
        
        except Exception as e:
            self.logger.error(f"통계 요약 조회 실패: {e}")
            raise DatabaseError(f"통계 데이터 조회 중 오류 발생: {e}")
    
    def _analyze_keywords(self, start_date: str, end_date: str, top_n: int = 10) -> list:
        """
        키워드 빈도 분석 (내부 메서드)
        
        Args:
            start_date: 시작일
            end_date: 종료일
            top_n: 상위 N개
            
        Returns:
            list: [(키워드, 빈도), ...]
        """
        try:
            texts = self.db.get_all_texts_in_period(start_date, end_date)
            
            if not texts:
                return []
            
            # 간단한 단어 추출 (공백 기준, 2글자 이상)
            all_words = []
            for text in texts:
                words = text.split()
                all_words.extend([w for w in words if len(w) >= 2])
            
            # 불용어 제거 (선택)
            stopwords = {"의", "를", "이", "가", "은", "는", "에", "에서", "으로", "로", "와", "과"}
            filtered_words = [w for w in all_words if w not in stopwords]
            
            # 빈도 계산
            word_counts = Counter(filtered_words)
            top_words = word_counts.most_common(top_n)
            
            return top_words
        
        except Exception as e:
            self.logger.warning(f"키워드 분석 실패: {e}")
            return []
    
    def _generate_charts(self, report: StatisticsReport, options: StatisticsOptions):
        """
        차트 생성 (내부 메서드)
        
        Args:
            report: 통계 보고서 데이터
            options: 분석 옵션
        """
        charts = {}
        
        try:
            # 부서별 막대 그래프
            if options.include_department and report.by_department:
                dept_chart = self.chart_generator.create_bar_chart(
                    report.by_department,
                    "부서별 VOC 현황",
                    xlabel="부서",
                    ylabel="건수",
                    filename=f"dept_{report.period_start}_{report.period_end}.png"
                )
                charts["department_bar"] = dept_chart
            
            # 상태별 파이 차트
            if options.include_status and report.by_status:
                status_chart = self.chart_generator.create_pie_chart(
                    report.by_status,
                    "상태별 VOC 분포",
                    filename=f"status_{report.period_start}_{report.period_end}.png"
                )
                charts["status_pie"] = status_chart
            
            # 기간별 선 그래프
            if report.by_date:
                period_chart = self.chart_generator.create_line_chart(
                    report.by_date,
                    "일별 VOC 발생 추이",
                    xlabel="날짜",
                    ylabel="건수",
                    filename=f"period_{report.period_start}_{report.period_end}.png"
                )
                charts["period_line"] = period_chart
            
            # 키워드 워드 클라우드 (또는 막대 그래프)
            if options.include_keywords and report.top_keywords:
                # 키워드를 텍스트로 변환 (빈도만큼 반복)
                keyword_texts = []
                for word, freq in report.top_keywords:
                    keyword_texts.extend([word] * freq)
                
                if keyword_texts:
                    keyword_chart = self.chart_generator.create_wordcloud(
                        keyword_texts,
                        "주요 키워드",
                        max_words=50,
                        filename=f"keywords_{report.period_start}_{report.period_end}.png"
                    )
                    charts["keywords_cloud"] = keyword_chart
            
            # 차트 경로를 report에 저장
            report.charts = charts
        
        except Exception as e:
            self.logger.error(f"차트 생성 중 오류: {e}")
            raise
    
    def export_to_excel(self, report: StatisticsReport, output_dir: str) -> Tuple[bool, str]:
        """
        Excel 파일 생성
        
        Args:
            report: 통계 보고서 데이터
            output_dir: 저장 디렉토리
            
        Returns:
            tuple[bool, str]: (성공 여부, 파일 경로 또는 에러 메시지)
        """
        try:
            import openpyxl
            from openpyxl.styles import Font, Alignment, PatternFill
            from openpyxl.utils import get_column_letter
        except ImportError:
            return (False, "openpyxl 라이브러리가 설치되지 않았습니다.")
        
        try:
            # 워크북 생성
            wb = openpyxl.Workbook()
            ws = wb.active
            ws.title = "VOC 통계"
            
            # 헤더 스타일
            header_fill = PatternFill(start_color="4472C4", end_color="4472C4", fill_type="solid")
            header_font = Font(bold=True, color="FFFFFF", size=12)
            header_align = Alignment(horizontal="center", vertical="center")
            
            # 1. 요약 정보
            ws.append(["VOC 통계 분석 보고서"])
            ws.merge_cells('A1:D1')
            ws['A1'].font = Font(bold=True, size=14)
            ws['A1'].alignment = Alignment(horizontal="center")
            
            ws.append([])
            ws.append(["분석 기간", f"{report.period_start} ~ {report.period_end}"])
            ws.append(["총 VOC 건수", report.total_count])
            ws.append(["생성 시각", report.generated_at])
            ws.append([])
            
            # 2. 부서별 통계
            if report.by_department:
                ws.append(["부서별 통계"])
                ws.merge_cells(f'A{ws.max_row}:C{ws.max_row}')
                ws[f'A{ws.max_row}'].font = Font(bold=True, size=12)
                
                header_row = ws.max_row + 1
                ws.append(["부서", "건수", "비율(%)"])
                for col in range(1, 4):
                    cell = ws.cell(row=header_row, column=col)
                    cell.fill = header_fill
                    cell.font = header_font
                    cell.alignment = header_align
                
                total = sum(report.by_department.values())
                for dept, count in sorted(report.by_department.items(), key=lambda x: x[1], reverse=True):
                    percentage = round(count / total * 100, 2) if total > 0 else 0
                    ws.append([dept, count, percentage])
                
                ws.append([])
            
            # 3. 상태별 통계
            if report.by_status:
                ws.append(["상태별 통계"])
                ws.merge_cells(f'A{ws.max_row}:C{ws.max_row}')
                ws[f'A{ws.max_row}'].font = Font(bold=True, size=12)
                
                header_row = ws.max_row + 1
                ws.append(["상태", "건수", "비율(%)"])
                for col in range(1, 4):
                    cell = ws.cell(row=header_row, column=col)
                    cell.fill = header_fill
                    cell.font = header_font
                    cell.alignment = header_align
                
                total = sum(report.by_status.values())
                for status, count in sorted(report.by_status.items(), key=lambda x: x[1], reverse=True):
                    percentage = round(count / total * 100, 2) if total > 0 else 0
                    ws.append([status, count, percentage])
                
                ws.append([])
            
            # 4. 주요 키워드
            if report.top_keywords:
                ws.append(["주요 키워드 TOP 10"])
                ws.merge_cells(f'A{ws.max_row}:B{ws.max_row}')
                ws[f'A{ws.max_row}'].font = Font(bold=True, size=12)
                
                header_row = ws.max_row + 1
                ws.append(["키워드", "빈도"])
                for col in range(1, 3):
                    cell = ws.cell(row=header_row, column=col)
                    cell.fill = header_fill
                    cell.font = header_font
                    cell.alignment = header_align
                
                for keyword, freq in report.top_keywords:
                    ws.append([keyword, freq])
            
            # 열 너비 자동 조정
            for col in range(1, ws.max_column + 1):
                ws.column_dimensions[get_column_letter(col)].width = 20
            
            # 파일 저장
            filename = f"VOC_통계_{report.period_start}_{report.period_end}.xlsx"
            filepath = Path(output_dir) / filename
            wb.save(filepath)
            
            self.logger.info(f"Excel 보고서 생성 완료: {filepath}")
            return (True, str(filepath))
        
        except Exception as e:
            self.logger.error(f"Excel 생성 실패: {e}")
            return (False, f"Excel 파일 생성 중 오류 발생: {e}")
    
    def export_to_pdf(self, report: StatisticsReport, output_dir: str) -> Tuple[bool, str]:
        """
        PDF 파일 생성
        
        Args:
            report: 통계 보고서 데이터
            output_dir: 저장 디렉토리
            
        Returns:
            tuple[bool, str]: (성공 여부, 파일 경로 또는 에러 메시지)
        """
        try:
            from reportlab.lib import colors
            from reportlab.lib.pagesizes import A4, landscape
            from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
            from reportlab.lib.units import cm
            from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer, Image, PageBreak
            from reportlab.pdfbase import pdfmetrics
            from reportlab.pdfbase.ttfonts import TTFont
            from reportlab.lib.enums import TA_CENTER, TA_LEFT
        except ImportError:
            return (False, "reportlab 라이브러리가 설치되지 않았습니다.\npip install reportlab")
        
        try:
            # 한글 폰트 설정
            font_registered = self._register_korean_font()
            font_name = "Malgun" if font_registered else "Helvetica"
            
            # PDF 파일 경로
            filename = f"VOC_통계_{report.period_start}_{report.period_end}.pdf"
            filepath = Path(output_dir) / filename
            
            # PDF 문서 생성 (A4 가로)
            doc = SimpleDocTemplate(
                str(filepath),
                pagesize=landscape(A4),
                rightMargin=2*cm,
                leftMargin=2*cm,
                topMargin=2*cm,
                bottomMargin=2*cm
            )
            
            # 스토리 (콘텐츠) 리스트
            story = []
            
            # 스타일 정의
            styles = getSampleStyleSheet()
            
            # 제목 스타일
            title_style = ParagraphStyle(
                'CustomTitle',
                parent=styles['Heading1'],
                fontName=font_name,
                fontSize=20,
                textColor=colors.HexColor('#1e40af'),
                spaceAfter=30,
                alignment=TA_CENTER
            )
            
            # 섹션 제목 스타일
            section_style = ParagraphStyle(
                'SectionTitle',
                parent=styles['Heading2'],
                fontName=font_name,
                fontSize=14,
                textColor=colors.HexColor('#1e40af'),
                spaceAfter=12,
                spaceBefore=20
            )
            
            # 본문 스타일
            body_style = ParagraphStyle(
                'CustomBody',
                parent=styles['Normal'],
                fontName=font_name,
                fontSize=10,
                leading=14
            )
            
            # 1. 제목
            story.append(Paragraph("📊 VOC 통계 분석 보고서", title_style))
            story.append(Spacer(1, 0.5*cm))
            
            # 2. 요약 정보 테이블
            summary_data = [
                ['분석 기간', f"{report.period_start} ~ {report.period_end}"],
                ['총 VOC 건수', f"{report.total_count}건"],
                ['생성 시각', report.generated_at]
            ]
            
            summary_table = Table(summary_data, colWidths=[5*cm, 10*cm])
            summary_table.setStyle(TableStyle([
                ('BACKGROUND', (0, 0), (0, -1), colors.HexColor('#e0e7ff')),
                ('TEXTCOLOR', (0, 0), (-1, -1), colors.black),
                ('ALIGN', (0, 0), (-1, -1), 'LEFT'),
                ('FONTNAME', (0, 0), (-1, -1), font_name),
                ('FONTSIZE', (0, 0), (-1, -1), 11),
                ('BOTTOMPADDING', (0, 0), (-1, -1), 12),
                ('TOPPADDING', (0, 0), (-1, -1), 12),
                ('GRID', (0, 0), (-1, -1), 1, colors.grey)
            ]))
            
            story.append(summary_table)
            story.append(Spacer(1, 1*cm))
            
            # 3. 부서별 통계
            if report.by_department:
                story.append(Paragraph("부서별 통계", section_style))
                
                dept_data = [['부서', '건수', '비율(%)']]
                total = sum(report.by_department.values())
                
                for dept, count in sorted(report.by_department.items(), key=lambda x: x[1], reverse=True):
                    percentage = round(count / total * 100, 2) if total > 0 else 0
                    dept_data.append([dept, str(count), f"{percentage}%"])
                
                dept_table = Table(dept_data, colWidths=[8*cm, 4*cm, 4*cm])
                dept_table.setStyle(TableStyle([
                    ('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#4472C4')),
                    ('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
                    ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
                    ('FONTNAME', (0, 0), (-1, 0), font_name),
                    ('FONTSIZE', (0, 0), (-1, 0), 12),
                    ('FONTNAME', (0, 1), (-1, -1), font_name),
                    ('FONTSIZE', (0, 1), (-1, -1), 10),
                    ('BOTTOMPADDING', (0, 0), (-1, -1), 10),
                    ('TOPPADDING', (0, 0), (-1, -1), 10),
                    ('GRID', (0, 0), (-1, -1), 1, colors.grey),
                    ('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.HexColor('#f3f4f6')])
                ]))
                
                story.append(dept_table)
                story.append(Spacer(1, 0.5*cm))
                
                # 부서별 차트 삽입
                if 'department_bar' in report.charts:
                    try:
                        img = Image(report.charts['department_bar'], width=14*cm, height=9*cm)
                        story.append(img)
                        story.append(Spacer(1, 0.5*cm))
                    except Exception as e:
                        self.logger.warning(f"차트 이미지 삽입 실패: {e}")
            
            # 페이지 나누기
            story.append(PageBreak())
            
            # 4. 상태별 통계
            if report.by_status:
                story.append(Paragraph("상태별 통계", section_style))
                
                status_data = [['상태', '건수', '비율(%)']]
                total = sum(report.by_status.values())
                
                for status, count in sorted(report.by_status.items(), key=lambda x: x[1], reverse=True):
                    percentage = round(count / total * 100, 2) if total > 0 else 0
                    status_data.append([status, str(count), f"{percentage}%"])
                
                status_table = Table(status_data, colWidths=[8*cm, 4*cm, 4*cm])
                status_table.setStyle(TableStyle([
                    ('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#4472C4')),
                    ('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
                    ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
                    ('FONTNAME', (0, 0), (-1, 0), font_name),
                    ('FONTSIZE', (0, 0), (-1, 0), 12),
                    ('FONTNAME', (0, 1), (-1, -1), font_name),
                    ('FONTSIZE', (0, 1), (-1, -1), 10),
                    ('BOTTOMPADDING', (0, 0), (-1, -1), 10),
                    ('TOPPADDING', (0, 0), (-1, -1), 10),
                    ('GRID', (0, 0), (-1, -1), 1, colors.grey),
                    ('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.HexColor('#f3f4f6')])
                ]))
                
                story.append(status_table)
                story.append(Spacer(1, 0.5*cm))
                
                # 상태별 차트 삽입
                if 'status_pie' in report.charts:
                    try:
                        img = Image(report.charts['status_pie'], width=14*cm, height=9*cm)
                        story.append(img)
                        story.append(Spacer(1, 0.5*cm))
                    except Exception as e:
                        self.logger.warning(f"차트 이미지 삽입 실패: {e}")
            
            # 5. 기간별 추이 차트
            if 'period_line' in report.charts:
                story.append(PageBreak())
                story.append(Paragraph("일별 VOC 발생 추이", section_style))
                
                try:
                    img = Image(report.charts['period_line'], width=18*cm, height=11*cm)
                    story.append(img)
                    story.append(Spacer(1, 0.5*cm))
                except Exception as e:
                    self.logger.warning(f"차트 이미지 삽입 실패: {e}")
            
            # 6. 주요 키워드
            if report.top_keywords:
                story.append(PageBreak())
                story.append(Paragraph("주요 키워드 TOP 10", section_style))
                
                keyword_data = [['순위', '키워드', '빈도']]
                for i, (keyword, freq) in enumerate(report.top_keywords[:10], 1):
                    keyword_data.append([str(i), keyword, str(freq)])
                
                keyword_table = Table(keyword_data, colWidths=[3*cm, 8*cm, 5*cm])
                keyword_table.setStyle(TableStyle([
                    ('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#4472C4')),
                    ('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
                    ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
                    ('FONTNAME', (0, 0), (-1, 0), font_name),
                    ('FONTSIZE', (0, 0), (-1, 0), 12),
                    ('FONTNAME', (0, 1), (-1, -1), font_name),
                    ('FONTSIZE', (0, 1), (-1, -1), 10),
                    ('BOTTOMPADDING', (0, 0), (-1, -1), 10),
                    ('TOPPADDING', (0, 0), (-1, -1), 10),
                    ('GRID', (0, 0), (-1, -1), 1, colors.grey),
                    ('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.HexColor('#f3f4f6')])
                ]))
                
                story.append(keyword_table)
                story.append(Spacer(1, 0.5*cm))
                
                # 키워드 워드클라우드 삽입
                if 'keywords_cloud' in report.charts:
                    try:
                        img = Image(report.charts['keywords_cloud'], width=16*cm, height=10*cm)
                        story.append(img)
                    except Exception as e:
                        self.logger.warning(f"차트 이미지 삽입 실패: {e}")
            
            # PDF 빌드
            doc.build(story)
            
            self.logger.info(f"PDF 보고서 생성 완료: {filepath}")
            return (True, str(filepath))
        
        except Exception as e:
            self.logger.error(f"PDF 생성 실패: {e}", exc_info=True)
            return (False, f"PDF 파일 생성 중 오류 발생: {e}")
    
    def _register_korean_font(self) -> bool:
        """
        한글 폰트 등록 (내부 메서드)
        
        Returns:
            bool: 등록 성공 여부
        """
        try:
            from reportlab.pdfbase import pdfmetrics
            from reportlab.pdfbase.ttfonts import TTFont
            import matplotlib.font_manager as fm
            
            # 시스템 폰트에서 맑은 고딕 찾기
            font_candidates = [
                'Malgun Gothic',
                'NanumGothic',
                'NanumBarunGothic',
                'Gulim',
                'Dotum'
            ]
            
            for font_name in font_candidates:
                for font in fm.fontManager.ttflist:
                    if font.name == font_name:
                        try:
                            pdfmetrics.registerFont(TTFont('Malgun', font.fname))
                            self.logger.info(f"PDF 한글 폰트 등록 성공: {font_name}")
                            return True
                        except Exception as e:
                            self.logger.warning(f"폰트 등록 실패 ({font_name}): {e}")
                            continue
            
            self.logger.warning("한글 폰트를 찾을 수 없습니다. 기본 폰트를 사용합니다.")
            return False
        
        except Exception as e:
            self.logger.error(f"폰트 등록 중 오류: {e}")
            return False

