"""
차트 생성 유틸리티

matplotlib 및 wordcloud를 사용하여 VOC 통계 차트를 생성합니다.
한글 폰트 설정, 메모리 관리, 파일 저장 등을 처리합니다.

주요 기능:
    - 막대 그래프 (부서별, 상태별)
    - 선 그래프 (시계열 추이)
    - 파이 차트 (비율)
    - 워드 클라우드 (키워드)

작성자: KH.Choi
최종 수정: 2026-02-18
버전: 1.0
"""
import os
from pathlib import Path
from typing import Dict, List, Optional
import matplotlib
matplotlib.use('Agg')  # GUI 없이 백그라운드에서 실행
import matplotlib.pyplot as plt
import matplotlib.font_manager as fm
from collections import Counter

from utils.logger import get_logger
from utils.path_utils import get_data_dir


class ChartGenerator:
    """
    차트 생성 유틸리티 클래스
    
    matplotlib을 사용하여 다양한 형태의 차트를 생성합니다.
    한글 폰트 설정, 메모리 관리, 파일 저장을 자동으로 처리합니다.
    
    Attributes:
        logger: 로거 인스턴스
        output_dir (Path): 차트 저장 디렉토리
        font_name (str): 사용할 한글 폰트명
    
    주요 메서드:
        create_bar_chart: 막대 그래프 생성
        create_line_chart: 선 그래프 생성
        create_pie_chart: 파이 차트 생성
        create_wordcloud: 워드 클라우드 생성
    
    사용 예시:
        >>> generator = ChartGenerator()
        >>> data = {"차량": 80, "시설": 50, "역무": 20}
        >>> path = generator.create_bar_chart(data, "부서별 VOC 현황")
    """
    
    def __init__(self, output_dir: Optional[Path] = None):
        """
        차트 생성기 초기화
        
        Args:
            output_dir (Path, optional): 차트 저장 디렉토리. 기본값은 data/charts
        """
        self.logger = get_logger("ChartGenerator")
        self.output_dir = output_dir or (get_data_dir() / "charts")
        self.output_dir.mkdir(parents=True, exist_ok=True)
        
        # 한글 폰트 설정
        self._setup_korean_font()
    
    def _setup_korean_font(self):
        """
        한글 폰트 설정
        
        시스템에 설치된 한글 폰트를 찾아 matplotlib에 설정합니다.
        폰트를 찾지 못하면 기본 폰트를 사용합니다.
        """
        try:
            # Windows 기본 한글 폰트 시도
            font_candidates = [
                'Malgun Gothic',  # 맑은 고딕
                'NanumGothic',    # 나눔고딕
                'NanumBarunGothic',
                'Gulim',          # 굴림
                'Dotum'           # 돋움
            ]
            
            available_fonts = [f.name for f in fm.fontManager.ttflist]
            
            for font in font_candidates:
                if font in available_fonts:
                    plt.rcParams['font.family'] = font
                    self.font_name = font
                    self.logger.info(f"한글 폰트 설정: {font}")
                    break
            else:
                # 한글 폰트를 찾지 못한 경우
                self.logger.warning("한글 폰트를 찾을 수 없습니다. 기본 폰트를 사용합니다.")
                self.font_name = plt.rcParams['font.family']
            
            # 마이너스 기호 깨짐 방지
            plt.rcParams['axes.unicode_minus'] = False
        
        except Exception as e:
            self.logger.error(f"폰트 설정 실패: {e}")
            self.font_name = None
    
    def create_bar_chart(
        self, 
        data: Dict[str, int], 
        title: str,
        xlabel: str = "항목",
        ylabel: str = "건수",
        filename: Optional[str] = None
    ) -> str:
        """
        막대 그래프 생성
        
        Args:
            data: 데이터 딕셔너리 {항목명: 값}
            title: 차트 제목
            xlabel: X축 레이블
            ylabel: Y축 레이블
            filename: 저장 파일명 (선택, 기본값은 자동 생성)
            
        Returns:
            str: 저장된 파일 경로
        
        Raises:
            ValueError: 데이터가 비어있는 경우
        """
        if not data:
            raise ValueError("차트 생성을 위한 데이터가 비어있습니다.")
        
        try:
            # 그래프 생성
            fig, ax = plt.subplots(figsize=(10, 6))
            
            items = list(data.keys())
            values = list(data.values())
            
            bars = ax.bar(items, values, color='steelblue', alpha=0.8)
            
            # 막대 위에 값 표시
            for bar in bars:
                height = bar.get_height()
                ax.text(
                    bar.get_x() + bar.get_width() / 2.,
                    height,
                    f'{int(height)}',
                    ha='center',
                    va='bottom',
                    fontsize=10
                )
            
            ax.set_xlabel(xlabel, fontsize=12)
            ax.set_ylabel(ylabel, fontsize=12)
            ax.set_title(title, fontsize=14, fontweight='bold')
            ax.grid(axis='y', alpha=0.3)
            
            plt.xticks(rotation=45, ha='right')
            plt.tight_layout()
            
            # 파일 저장
            if not filename:
                filename = f"bar_chart_{title.replace(' ', '_')}.png"
            
            filepath = self.output_dir / filename
            plt.savefig(filepath, dpi=150, bbox_inches='tight')
            plt.close(fig)  # 메모리 해제
            
            self.logger.info(f"막대 그래프 생성 완료: {filepath}")
            return str(filepath)
        
        except Exception as e:
            self.logger.error(f"막대 그래프 생성 실패: {e}")
            plt.close('all')  # 에러 시에도 메모리 해제
            raise
    
    def create_line_chart(
        self,
        data: Dict[str, int],
        title: str,
        xlabel: str = "날짜",
        ylabel: str = "건수",
        filename: Optional[str] = None
    ) -> str:
        """
        선 그래프 생성 (시계열 데이터)
        
        Args:
            data: 데이터 딕셔너리 {날짜: 값}
            title: 차트 제목
            xlabel: X축 레이블
            ylabel: Y축 레이블
            filename: 저장 파일명 (선택)
            
        Returns:
            str: 저장된 파일 경로
        """
        if not data:
            raise ValueError("차트 생성을 위한 데이터가 비어있습니다.")
        
        try:
            fig, ax = plt.subplots(figsize=(12, 6))
            
            dates = list(data.keys())
            values = list(data.values())
            
            ax.plot(dates, values, marker='o', linewidth=2, markersize=6, color='steelblue')
            
            # 데이터 포인트에 값 표시 (데이터가 많지 않을 때만)
            if len(dates) <= 31:
                for i, (date, value) in enumerate(zip(dates, values)):
                    ax.text(i, value, str(value), ha='center', va='bottom', fontsize=9)
            
            ax.set_xlabel(xlabel, fontsize=12)
            ax.set_ylabel(ylabel, fontsize=12)
            ax.set_title(title, fontsize=14, fontweight='bold')
            ax.grid(True, alpha=0.3)
            
            # X축 레이블 회전 (날짜가 많을 때)
            if len(dates) > 10:
                plt.xticks(rotation=45, ha='right')
            
            plt.tight_layout()
            
            if not filename:
                filename = f"line_chart_{title.replace(' ', '_')}.png"
            
            filepath = self.output_dir / filename
            plt.savefig(filepath, dpi=150, bbox_inches='tight')
            plt.close(fig)
            
            self.logger.info(f"선 그래프 생성 완료: {filepath}")
            return str(filepath)
        
        except Exception as e:
            self.logger.error(f"선 그래프 생성 실패: {e}")
            plt.close('all')
            raise
    
    def create_pie_chart(
        self,
        data: Dict[str, int],
        title: str,
        filename: Optional[str] = None
    ) -> str:
        """
        파이 차트 생성
        
        Args:
            data: 데이터 딕셔너리 {항목명: 값}
            title: 차트 제목
            filename: 저장 파일명 (선택)
            
        Returns:
            str: 저장된 파일 경로
        """
        if not data:
            raise ValueError("차트 생성을 위한 데이터가 비어있습니다.")
        
        try:
            fig, ax = plt.subplots(figsize=(10, 8))
            
            labels = list(data.keys())
            sizes = list(data.values())
            
            # 색상 팔레트
            colors = plt.cm.Set3(range(len(labels)))
            
            # 파이 차트 생성
            wedges, texts, autotexts = ax.pie(
                sizes,
                labels=labels,
                autopct='%1.1f%%',
                startangle=90,
                colors=colors,
                textprops={'fontsize': 11}
            )
            
            # 퍼센트 텍스트 스타일
            for autotext in autotexts:
                autotext.set_color('white')
                autotext.set_fontweight('bold')
            
            ax.set_title(title, fontsize=14, fontweight='bold', pad=20)
            
            plt.tight_layout()
            
            if not filename:
                filename = f"pie_chart_{title.replace(' ', '_')}.png"
            
            filepath = self.output_dir / filename
            plt.savefig(filepath, dpi=150, bbox_inches='tight')
            plt.close(fig)
            
            self.logger.info(f"파이 차트 생성 완료: {filepath}")
            return str(filepath)
        
        except Exception as e:
            self.logger.error(f"파이 차트 생성 실패: {e}")
            plt.close('all')
            raise
    
    def create_wordcloud(
        self,
        texts: List[str],
        title: str,
        max_words: int = 50,
        filename: Optional[str] = None
    ) -> str:
        """
        워드 클라우드 생성
        
        Args:
            texts: 텍스트 리스트
            title: 차트 제목
            max_words: 최대 단어 수
            filename: 저장 파일명 (선택)
            
        Returns:
            str: 저장된 파일 경로
        
        Note:
            wordcloud 라이브러리가 설치되어 있어야 합니다.
            한글 불용어 제거 및 빈도 기반 생성을 지원합니다.
        """
        if not texts:
            raise ValueError("워드 클라우드 생성을 위한 텍스트가 비어있습니다.")
        
        try:
            # wordcloud 라이브러리 import (선택적 의존성)
            try:
                from wordcloud import WordCloud
            except ImportError:
                self.logger.warning("wordcloud 라이브러리가 설치되지 않았습니다. 간단한 빈도 차트를 생성합니다.")
                return self._create_keyword_bar_chart(texts, title, max_words, filename)
            
            # 텍스트 전처리 및 단어 추출
            word_freq = self._extract_word_frequencies(texts)
            
            if not word_freq:
                self.logger.warning("추출된 키워드가 없습니다. 빈 차트를 생성합니다.")
                raise ValueError("유효한 키워드가 없습니다.")
            
            # 워드 클라우드 생성 (빈도 딕셔너리 사용)
            wordcloud = WordCloud(
                font_path=self._get_font_path(),
                width=1200,
                height=800,
                background_color='white',
                max_words=max_words,
                relative_scaling=0.3,
                min_font_size=10,
                colormap='viridis',
                prefer_horizontal=0.7,
                collocations=False  # 단어 조합 비활성화
            ).generate_from_frequencies(word_freq)
            
            # 그래프 생성
            fig, ax = plt.subplots(figsize=(14, 10))
            ax.imshow(wordcloud, interpolation='bilinear')
            ax.axis('off')
            ax.set_title(title, fontsize=16, fontweight='bold', pad=20)
            
            plt.tight_layout(pad=0)
            
            if not filename:
                filename = f"wordcloud_{title.replace(' ', '_')}.png"
            
            filepath = self.output_dir / filename
            plt.savefig(filepath, dpi=200, bbox_inches='tight', facecolor='white')
            plt.close(fig)
            
            self.logger.info(f"워드 클라우드 생성 완료: {filepath} (단어 수: {len(word_freq)})")
            return str(filepath)
        
        except Exception as e:
            self.logger.error(f"워드 클라우드 생성 실패: {e}")
            plt.close('all')
            raise
    
    def _extract_word_frequencies(self, texts: List[str]) -> dict:
        """
        텍스트에서 단어 빈도 추출 (내부 메서드)
        
        Args:
            texts: 텍스트 리스트
            
        Returns:
            dict: {단어: 빈도}
        """
        # 한글 불용어 (조사, 접속사 등)
        stopwords = {
            # 조사
            "의", "를", "이", "가", "은", "는", "에", "에서", "으로", "로", "와", "과",
            "도", "만", "까지", "부터", "께서", "에게", "한테", "보다", "처럼",
            # 접속사/부사
            "그리고", "그러나", "하지만", "또한", "또", "및", "등", "즉",
            # 기타
            "것", "수", "등등", "때문", "위해", "대한", "통해", "관련", "경우",
            "있습니다", "없습니다", "합니다", "됩니다", "입니다",
            # VOC 관련 일반 단어 (너무 흔한 것들)
            "고객", "민원", "문의", "요청", "답변", "처리", "확인"
        }
        
        all_words = []
        
        for text in texts:
            if not text:
                continue
            
            # 공백 기준 단어 분리
            words = text.split()
            
            for word in words:
                # 길이 체크 (2글자 이상, 15글자 이하)
                if len(word) < 2 or len(word) > 15:
                    continue
                
                # 불용어 제거
                if word in stopwords:
                    continue
                
                # 숫자만 있는 단어 제거
                if word.isdigit():
                    continue
                
                # 특수문자만 있는 단어 제거
                if not any(c.isalnum() for c in word):
                    continue
                
                all_words.append(word)
        
        # 빈도 계산
        word_counts = Counter(all_words)
        
        # 빈도가 2 이상인 단어만 (너무 드문 단어 제거)
        filtered_counts = {word: count for word, count in word_counts.items() if count >= 2}
        
        return filtered_counts
    
    def _create_keyword_bar_chart(
        self,
        texts: List[str],
        title: str,
        top_n: int = 10,
        filename: Optional[str] = None
    ) -> str:
        """
        키워드 빈도 막대 그래프 생성 (워드 클라우드 대체)
        
        Args:
            texts: 텍스트 리스트
            title: 차트 제목
            top_n: 상위 N개 키워드
            filename: 저장 파일명
            
        Returns:
            str: 저장된 파일 경로
        """
        # 간단한 단어 추출 (공백 기준)
        all_words = []
        for text in texts:
            words = text.split()
            # 2글자 이상만 추출
            all_words.extend([w for w in words if len(w) >= 2])
        
        # 빈도 계산
        word_counts = Counter(all_words)
        top_words = dict(word_counts.most_common(top_n))
        
        # 막대 그래프 생성
        return self.create_bar_chart(
            top_words,
            f"{title} (TOP {top_n})",
            xlabel="키워드",
            ylabel="빈도",
            filename=filename or f"keyword_bar_{title.replace(' ', '_')}.png"
        )
    
    def _get_font_path(self) -> Optional[str]:
        """
        한글 폰트 경로 반환
        
        Returns:
            str: 폰트 파일 경로 (없으면 None)
        """
        if not self.font_name:
            return None
        
        for font in fm.fontManager.ttflist:
            if font.name == self.font_name:
                return font.fname
        
        return None
