"""
테스트 커버리지 분석
코드 커버리지를 측정하고 분석합니다.
"""

import unittest
import coverage
import os
import sys
import logging
from typing import Dict, List, Any

# 프로젝트 루트를 Python 경로에 추가
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))

logger = logging.getLogger(__name__)

class CoverageAnalyzer:
    """코드 커버리지 분석기"""
    
    def __init__(self, source_dir: str = 'src', exclude_patterns: List[str] = None):
        """
        커버리지 분석기 초기화
        
        Args:
            source_dir (str): 소스 코드 디렉토리
            exclude_patterns (List[str]): 제외할 패턴 리스트
        """
        self.source_dir = source_dir
        self.exclude_patterns = exclude_patterns or [
            '*/tests/*',
            '*/test_*',
            '*/__pycache__/*',
            '*/migrations/*',
            '*/venv/*',
            '*/env/*',
            '*/site-packages/*'
        ]
        
        # 커버리지 설정
        self.cov = coverage.Coverage(
            source=[source_dir],
            omit=self.exclude_patterns,
            branch=True,
            include=f'{source_dir}/*'
        )
    
    def start_coverage(self):
        """커버리지 측정 시작"""
        self.cov.start()
        logger.info("커버리지 측정을 시작했습니다.")
    
    def stop_coverage(self):
        """커버리지 측정 중지"""
        self.cov.stop()
        logger.info("커버리지 측정을 중지했습니다.")
    
    def generate_report(self, output_dir: str = 'coverage_report') -> Dict[str, Any]:
        """
        커버리지 리포트 생성
        
        Args:
            output_dir (str): 리포트 출력 디렉토리
            
        Returns:
            Dict[str, Any]: 커버리지 리포트 데이터
        """
        try:
            # 출력 디렉토리 생성
            os.makedirs(output_dir, exist_ok=True)
            
            # HTML 리포트 생성
            html_report_path = os.path.join(output_dir, 'htmlcov')
            self.cov.html_report(directory=html_report_path)
            
            # XML 리포트 생성
            xml_report_path = os.path.join(output_dir, 'coverage.xml')
            self.cov.xml_report(outfile=xml_report_path)
            
            # JSON 리포트 생성
            json_report_path = os.path.join(output_dir, 'coverage.json')
            self.cov.json_report(outfile=json_report_path)
            
            # 텍스트 리포트 생성
            text_report_path = os.path.join(output_dir, 'coverage.txt')
            with open(text_report_path, 'w') as f:
                self.cov.report(file=f)
            
            # 커버리지 데이터 수집
            coverage_data = self._collect_coverage_data()
            
            logger.info(f"커버리지 리포트가 생성되었습니다: {output_dir}")
            return coverage_data
            
        except Exception as e:
            logger.error(f"커버리지 리포트 생성 중 오류: {str(e)}")
            return {}
    
    def _collect_coverage_data(self) -> Dict[str, Any]:
        """커버리지 데이터 수집"""
        try:
            # 전체 커버리지 통계
            total_coverage = self.cov.report()
            
            # 파일별 커버리지 데이터
            file_coverage = {}
            for filename in self.cov.get_data().measured_files():
                if filename.startswith(os.path.abspath(self.source_dir)):
                    relative_path = os.path.relpath(filename, os.path.abspath(self.source_dir))
                    file_coverage[relative_path] = self._get_file_coverage(filename)
            
            # 모듈별 커버리지 통계
            module_stats = self._get_module_stats()
            
            return {
                'total_coverage': total_coverage,
                'file_coverage': file_coverage,
                'module_stats': module_stats,
                'summary': self._generate_summary(total_coverage, file_coverage)
            }
            
        except Exception as e:
            logger.error(f"커버리지 데이터 수집 중 오류: {str(e)}")
            return {}
    
    def _get_file_coverage(self, filename: str) -> Dict[str, Any]:
        """파일별 커버리지 정보"""
        try:
            analysis = self.cov.analysis2(filename)
            executed_lines = analysis[1]
            missing_lines = analysis[2]
            excluded_lines = analysis[3]
            
            total_lines = len(executed_lines) + len(missing_lines) + len(excluded_lines)
            covered_lines = len(executed_lines)
            
            if total_lines > 0:
                coverage_percent = (covered_lines / total_lines) * 100
            else:
                coverage_percent = 100.0
            
            return {
                'total_lines': total_lines,
                'covered_lines': covered_lines,
                'missing_lines': len(missing_lines),
                'excluded_lines': len(excluded_lines),
                'coverage_percent': coverage_percent,
                'missing_line_numbers': list(missing_lines)
            }
            
        except Exception as e:
            logger.error(f"파일 커버리지 분석 중 오류: {str(e)}")
            return {}
    
    def _get_module_stats(self) -> Dict[str, Any]:
        """모듈별 통계"""
        try:
            module_stats = {}
            
            for filename in self.cov.get_data().measured_files():
                if filename.startswith(os.path.abspath(self.source_dir)):
                    relative_path = os.path.relpath(filename, os.path.abspath(self.source_dir))
                    module_name = relative_path.replace(os.sep, '.').replace('.py', '')
                    
                    file_coverage = self._get_file_coverage(filename)
                    module_stats[module_name] = file_coverage
            
            return module_stats
            
        except Exception as e:
            logger.error(f"모듈 통계 수집 중 오류: {str(e)}")
            return {}
    
    def _generate_summary(self, total_coverage: float, file_coverage: Dict[str, Any]) -> Dict[str, Any]:
        """커버리지 요약 생성"""
        try:
            # 파일별 커버리지 통계
            coverage_percentages = [data['coverage_percent'] for data in file_coverage.values()]
            
            if coverage_percentages:
                avg_coverage = sum(coverage_percentages) / len(coverage_percentages)
                min_coverage = min(coverage_percentages)
                max_coverage = max(coverage_percentages)
            else:
                avg_coverage = 0.0
                min_coverage = 0.0
                max_coverage = 0.0
            
            # 커버리지 등급
            if total_coverage >= 90:
                grade = 'A'
            elif total_coverage >= 80:
                grade = 'B'
            elif total_coverage >= 70:
                grade = 'C'
            elif total_coverage >= 60:
                grade = 'D'
            else:
                grade = 'F'
            
            # 낮은 커버리지 파일 식별
            low_coverage_files = [
                filename for filename, data in file_coverage.items()
                if data['coverage_percent'] < 70
            ]
            
            return {
                'total_coverage': total_coverage,
                'average_coverage': avg_coverage,
                'min_coverage': min_coverage,
                'max_coverage': max_coverage,
                'grade': grade,
                'total_files': len(file_coverage),
                'low_coverage_files': low_coverage_files,
                'low_coverage_count': len(low_coverage_files)
            }
            
        except Exception as e:
            logger.error(f"요약 생성 중 오류: {str(e)}")
            return {}
    
    def check_coverage_threshold(self, threshold: float = 80.0) -> bool:
        """
        커버리지 임계값 확인
        
        Args:
            threshold (float): 임계값 (기본값: 80%)
            
        Returns:
            bool: 임계값 달성 여부
        """
        try:
            total_coverage = self.cov.report()
            return total_coverage >= threshold
            
        except Exception as e:
            logger.error(f"커버리지 임계값 확인 중 오류: {str(e)}")
            return False


class CoverageTestRunner:
    """커버리지 테스트 실행기"""
    
    def __init__(self, source_dir: str = 'src', test_dir: str = 'tests'):
        """
        커버리지 테스트 실행기 초기화
        
        Args:
            source_dir (str): 소스 코드 디렉토리
            test_dir (str): 테스트 디렉토리
        """
        self.source_dir = source_dir
        self.test_dir = test_dir
        self.analyzer = CoverageAnalyzer(source_dir)
    
    def run_coverage_tests(self, output_dir: str = 'coverage_report') -> Dict[str, Any]:
        """
        커버리지 테스트 실행
        
        Args:
            output_dir (str): 리포트 출력 디렉토리
            
        Returns:
            Dict[str, Any]: 테스트 결과 및 커버리지 데이터
        """
        try:
            logger.info("커버리지 테스트를 시작합니다...")
            
            # 커버리지 측정 시작
            self.analyzer.start_coverage()
            
            # 테스트 실행
            test_result = self._run_tests()
            
            # 커버리지 측정 중지
            self.analyzer.stop_coverage()
            
            # 커버리지 리포트 생성
            coverage_data = self.analyzer.generate_report(output_dir)
            
            # 결과 통합
            result = {
                'test_result': test_result,
                'coverage_data': coverage_data,
                'success': test_result['success'] and self.analyzer.check_coverage_threshold()
            }
            
            logger.info("커버리지 테스트가 완료되었습니다.")
            return result
            
        except Exception as e:
            logger.error(f"커버리지 테스트 실행 중 오류: {str(e)}")
            return {'success': False, 'error': str(e)}
    
    def _run_tests(self) -> Dict[str, Any]:
        """테스트 실행"""
        try:
            import subprocess
            
            # pytest 실행
            cmd = [
                sys.executable, '-m', 'pytest',
                self.test_dir,
                '-v',
                '--tb=short',
                '--maxfail=10'
            ]
            
            result = subprocess.run(cmd, capture_output=True, text=True)
            
            return {
                'success': result.returncode == 0,
                'returncode': result.returncode,
                'stdout': result.stdout,
                'stderr': result.stderr
            }
            
        except Exception as e:
            logger.error(f"테스트 실행 중 오류: {str(e)}")
            return {'success': False, 'error': str(e)}


class CoverageReportGenerator:
    """커버리지 리포트 생성기"""
    
    def __init__(self, coverage_data: Dict[str, Any]):
        """
        커버리지 리포트 생성기 초기화
        
        Args:
            coverage_data (Dict[str, Any]): 커버리지 데이터
        """
        self.coverage_data = coverage_data
    
    def generate_markdown_report(self, output_file: str = 'coverage_report.md'):
        """마크다운 리포트 생성"""
        try:
            with open(output_file, 'w', encoding='utf-8') as f:
                f.write("# 코드 커버리지 리포트\n\n")
                
                # 요약 정보
                summary = self.coverage_data.get('summary', {})
                f.write("## 요약\n\n")
                f.write(f"- **전체 커버리지**: {summary.get('total_coverage', 0):.1f}%\n")
                f.write(f"- **평균 커버리지**: {summary.get('average_coverage', 0):.1f}%\n")
                f.write(f"- **최소 커버리지**: {summary.get('min_coverage', 0):.1f}%\n")
                f.write(f"- **최대 커버리지**: {summary.get('max_coverage', 0):.1f}%\n")
                f.write(f"- **등급**: {summary.get('grade', 'F')}\n")
                f.write(f"- **총 파일 수**: {summary.get('total_files', 0)}\n")
                f.write(f"- **낮은 커버리지 파일 수**: {summary.get('low_coverage_count', 0)}\n\n")
                
                # 파일별 커버리지
                f.write("## 파일별 커버리지\n\n")
                f.write("| 파일 | 커버리지 | 총 라인 | 커버된 라인 | 누락된 라인 |\n")
                f.write("|------|----------|---------|-------------|-------------|\n")
                
                file_coverage = self.coverage_data.get('file_coverage', {})
                for filename, data in sorted(file_coverage.items()):
                    f.write(f"| {filename} | {data['coverage_percent']:.1f}% | "
                           f"{data['total_lines']} | {data['covered_lines']} | "
                           f"{data['missing_lines']} |\n")
                
                # 낮은 커버리지 파일
                low_coverage_files = summary.get('low_coverage_files', [])
                if low_coverage_files:
                    f.write("\n## 낮은 커버리지 파일 (< 70%)\n\n")
                    for filename in low_coverage_files:
                        data = file_coverage.get(filename, {})
                        f.write(f"- **{filename}**: {data.get('coverage_percent', 0):.1f}%\n")
                
                # 권장사항
                f.write("\n## 권장사항\n\n")
                if summary.get('total_coverage', 0) < 80:
                    f.write("- 전체 커버리지를 80% 이상으로 향상시키세요.\n")
                if low_coverage_files:
                    f.write("- 낮은 커버리지 파일에 대한 테스트를 추가하세요.\n")
                if summary.get('grade', 'F') in ['D', 'F']:
                    f.write("- 코드 품질을 개선하기 위해 더 많은 테스트를 작성하세요.\n")
            
            logger.info(f"마크다운 리포트가 생성되었습니다: {output_file}")
            
        except Exception as e:
            logger.error(f"마크다운 리포트 생성 중 오류: {str(e)}")
    
    def generate_json_report(self, output_file: str = 'coverage_report.json'):
        """JSON 리포트 생성"""
        try:
            import json
            
            with open(output_file, 'w', encoding='utf-8') as f:
                json.dump(self.coverage_data, f, indent=2, ensure_ascii=False)
            
            logger.info(f"JSON 리포트가 생성되었습니다: {output_file}")
            
        except Exception as e:
            logger.error(f"JSON 리포트 생성 중 오류: {str(e)}")


def main():
    """메인 함수"""
    # 로깅 설정
    logging.basicConfig(
        level=logging.INFO,
        format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
    )
    
    # 커버리지 테스트 실행
    runner = CoverageTestRunner()
    result = runner.run_coverage_tests()
    
    if result['success']:
        print("✅ 커버리지 테스트가 성공적으로 완료되었습니다!")
        
        # 리포트 생성
        coverage_data = result['coverage_data']
        report_generator = CoverageReportGenerator(coverage_data)
        report_generator.generate_markdown_report()
        report_generator.generate_json_report()
        
        # 요약 출력
        summary = coverage_data.get('summary', {})
        print(f"\n📊 커버리지 요약:")
        print(f"  전체 커버리지: {summary.get('total_coverage', 0):.1f}%")
        print(f"  등급: {summary.get('grade', 'F')}")
        print(f"  총 파일 수: {summary.get('total_files', 0)}")
        print(f"  낮은 커버리지 파일 수: {summary.get('low_coverage_count', 0)}")
        
    else:
        print("❌ 커버리지 테스트가 실패했습니다.")
        if 'error' in result:
            print(f"오류: {result['error']}")
    
    return result['success']


if __name__ == '__main__':
    success = main()
    sys.exit(0 if success else 1)
