"""
Part 15: 캡스톤 프로젝트 테스트

이 모듈은 캡스톤 프로젝트의 모든 기능에 대한 테스트를 포함합니다.
"""

import unittest
import tempfile
import shutil
import os
import json
import numpy as np
from pathlib import Path
from unittest.mock import patch, MagicMock

# 테스트할 모듈 import
import sys
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from part_15_capstone_project import CapstoneProject, ModelEvaluator, PortfolioGenerator


class TestCapstoneProject(unittest.TestCase):
    """CapstoneProject 클래스 테스트"""
    
    def setUp(self):
        """테스트 설정"""
        self.temp_dir = tempfile.mkdtemp()
        self.project_name = "test_project"
        self.project_type = "ml"
        
    def tearDown(self):
        """테스트 정리"""
        shutil.rmtree(self.temp_dir, ignore_errors=True)
        
    def test_capstone_project_initialization(self):
        """프로젝트 초기화 테스트"""
        with patch('part_15_capstone_project.Path') as mock_path:
            mock_path.return_value = Path(self.temp_dir) / self.project_name
            
            project = CapstoneProject(self.project_name, self.project_type)
            
            self.assertEqual(project.project_name, self.project_name)
            self.assertEqual(project.project_type, self.project_type)
            self.assertIn('project_name', project.config)
            self.assertIn('project_type', project.config)
            self.assertIn('created_at', project.config)
            self.assertIn('version', project.config)
            self.assertIn('status', project.config)
    
    def test_initialize_config(self):
        """설정 초기화 테스트"""
        project = CapstoneProject(self.project_name, self.project_type)
        config = project._initialize_config()
        
        expected_keys = ['project_name', 'project_type', 'created_at', 'version', 'status']
        for key in expected_keys:
            self.assertIn(key, config)
        
        self.assertEqual(config['project_name'], self.project_name)
        self.assertEqual(config['project_type'], self.project_type)
        self.assertEqual(config['version'], '1.0.0')
        self.assertEqual(config['status'], 'initialized')
    
    def test_create_project_structure_success(self):
        """프로젝트 구조 생성 성공 테스트"""
        with patch('part_15_capstone_project.Path') as mock_path:
            mock_project_path = Path(self.temp_dir) / self.project_name
            mock_path.return_value = mock_project_path
            
            project = CapstoneProject(self.project_name, self.project_type)
            result = project.create_project_structure()
            
            self.assertTrue(result)
    
    def test_create_project_structure_failure(self):
        """프로젝트 구조 생성 실패 테스트"""
        with patch('part_15_capstone_project.Path') as mock_path:
            # Path 생성 시 예외 발생하도록 설정
            mock_path.side_effect = Exception("Permission denied")
            
            # 예외가 발생하는지 확인
            with self.assertRaises(Exception):
                project = CapstoneProject(self.project_name, self.project_type)
    
    def test_create_readme(self):
        """README 파일 생성 테스트"""
        with patch('part_15_capstone_project.Path') as mock_path:
            # 실제 디렉토리 생성
            test_dir = os.path.join(self.temp_dir, self.project_name)
            os.makedirs(test_dir, exist_ok=True)
            
            mock_project_path = Path(test_dir)
            mock_path.return_value = mock_project_path
            
            project = CapstoneProject(self.project_name, self.project_type)
            
            # README 파일 생성
            project._create_readme()
            
            # 파일이 생성되었는지 확인
            readme_file = mock_project_path / "README.md"
            self.assertTrue(readme_file.exists())


class TestModelEvaluator(unittest.TestCase):
    """ModelEvaluator 클래스 테스트"""
    
    def setUp(self):
        """테스트 설정"""
        self.evaluator = ModelEvaluator()
        
        # 테스트 데이터 생성
        self.y_true_classification = np.array([0, 1, 0, 1, 0])
        self.y_pred_classification = np.array([0, 1, 0, 0, 1])
        
        self.y_true_regression = np.array([1.0, 2.0, 3.0, 4.0, 5.0])
        self.y_pred_regression = np.array([1.1, 1.9, 3.1, 3.9, 5.1])
    
    def test_model_evaluator_initialization(self):
        """모델 평가기 초기화 테스트"""
        self.assertEqual(self.evaluator.metrics, {})
        self.assertEqual(self.evaluator.results, {})
    
    @patch('sklearn.metrics')
    def test_evaluate_classification(self, mock_metrics):
        """분류 모델 평가 테스트"""
        # Mock 설정
        mock_metrics.accuracy_score.return_value = 0.8
        mock_metrics.precision_score.return_value = 0.75
        mock_metrics.recall_score.return_value = 0.7
        mock_metrics.f1_score.return_value = 0.72
        
        result = self.evaluator.evaluate_classification(
            self.y_true_classification, 
            self.y_pred_classification
        )
        
        # 결과 검증
        expected_metrics = ['accuracy', 'precision', 'recall', 'f1_score']
        for metric in expected_metrics:
            self.assertIn(metric, result)
        
        # 메트릭이 저장되었는지 확인
        self.assertIn('classification', self.evaluator.metrics)
    
    @patch('sklearn.metrics')
    def test_evaluate_regression(self, mock_metrics):
        """회귀 모델 평가 테스트"""
        # Mock 설정
        mock_metrics.mean_squared_error.return_value = 0.1
        mock_metrics.mean_absolute_error.return_value = 0.2
        mock_metrics.r2_score.return_value = 0.95
        
        result = self.evaluator.evaluate_regression(
            self.y_true_regression, 
            self.y_pred_regression
        )
        
        # 결과 검증
        expected_metrics = ['mse', 'rmse', 'mae', 'r2_score']
        for metric in expected_metrics:
            self.assertIn(metric, result)
        
        # 메트릭이 저장되었는지 확인
        self.assertIn('regression', self.evaluator.metrics)
    
    def test_generate_report_success(self):
        """리포트 생성 성공 테스트"""
        # 테스트 메트릭 추가
        self.evaluator.metrics = {
            'classification': {'accuracy': 0.85, 'precision': 0.82},
            'regression': {'mse': 0.1, 'r2_score': 0.95}
        }
        
        with tempfile.NamedTemporaryFile(mode='w', suffix='.html', delete=False) as f:
            output_path = f.name
        
        try:
            # plotly가 없어도 HTML 리포트는 생성됨
            result = self.evaluator.generate_report(output_path)
            
            # 결과가 비어있지 않으면 성공
            self.assertNotEqual(result, "")
            
            # 파일이 생성되었는지 확인
            if result:
                self.assertTrue(os.path.exists(result))
                
        finally:
            if os.path.exists(output_path):
                os.unlink(output_path)
    
    def test_generate_report_failure(self):
        """리포트 생성 실패 테스트"""
        # plotly import 실패 시뮬레이션
        with patch('builtins.__import__') as mock_import:
            mock_import.side_effect = ImportError("plotly not available")
            
            result = self.evaluator.generate_report()
            
            # 실패 시 빈 문자열 반환
            self.assertEqual(result, "")
    
    def test_model_evaluator_empty_data(self):
        """빈 데이터로 모델 평가 테스트"""
        evaluator = ModelEvaluator()
        
        # 빈 배열로 테스트
        empty_array = np.array([])
        
        # 빈 데이터로 평가 시도
        with self.assertRaises(ValueError):
            evaluator.evaluate_classification(empty_array, empty_array)


class TestPortfolioGenerator(unittest.TestCase):
    """PortfolioGenerator 클래스 테스트"""
    
    def setUp(self):
        """테스트 설정"""
        self.student_name = "테스트 학생"
        self.portfolio = PortfolioGenerator(self.student_name)
    
    def test_portfolio_generator_initialization(self):
        """포트폴리오 생성기 초기화 테스트"""
        self.assertEqual(self.portfolio.student_name, self.student_name)
        self.assertEqual(self.portfolio.projects, [])
        self.assertEqual(self.portfolio.skills, [])
    
    def test_add_project(self):
        """프로젝트 추가 테스트"""
        project_info = {
            "name": "테스트 프로젝트",
            "description": "테스트 설명",
            "technologies": "Python, TensorFlow",
            "results": "정확도 90%"
        }
        
        self.portfolio.add_project(project_info)
        
        self.assertEqual(len(self.portfolio.projects), 1)
        self.assertEqual(self.portfolio.projects[0], project_info)
    
    def test_add_skill(self):
        """스킬 추가 테스트"""
        skill = "Python"
        level = "Advanced"
        
        self.portfolio.add_skill(skill, level)
        
        self.assertEqual(len(self.portfolio.skills), 1)
        self.assertEqual(self.portfolio.skills[0]["skill"], skill)
        self.assertEqual(self.portfolio.skills[0]["level"], level)
    
    def test_add_skill_default_level(self):
        """스킬 추가 기본 레벨 테스트"""
        skill = "Machine Learning"
        
        self.portfolio.add_skill(skill)
        
        self.assertEqual(len(self.portfolio.skills), 1)
        self.assertEqual(self.portfolio.skills[0]["skill"], skill)
        self.assertEqual(self.portfolio.skills[0]["level"], "Intermediate")
    
    def test_generate_portfolio_success(self):
        """포트폴리오 생성 성공 테스트"""
        # 테스트 데이터 추가
        self.portfolio.add_skill("Python", "Advanced")
        self.portfolio.add_skill("Machine Learning", "Intermediate")
        
        self.portfolio.add_project({
            "name": "감정 분석 모델",
            "description": "텍스트 기반 감정 분석",
            "technologies": "Python, TensorFlow",
            "results": "정확도 85%"
        })
        
        with tempfile.NamedTemporaryFile(mode='w', suffix='.html', delete=False) as f:
            output_path = f.name
        
        try:
            result = self.portfolio.generate_portfolio(output_path)
            
            self.assertEqual(result, output_path)
            self.assertTrue(os.path.exists(output_path))
            
            # 파일 내용 확인
            with open(output_path, 'r', encoding='utf-8') as f:
                content = f.read()
                self.assertIn(self.student_name, content)
                self.assertIn('AI 포트폴리오', content)
                self.assertIn('Python', content)
                self.assertIn('Machine Learning', content)
                self.assertIn('감정 분석 모델', content)
                
        finally:
            if os.path.exists(output_path):
                os.unlink(output_path)
    
    def test_generate_portfolio_with_empty_data(self):
        """빈 데이터로 포트폴리오 생성 테스트"""
        with tempfile.NamedTemporaryFile(mode='w', suffix='.html', delete=False) as f:
            output_path = f.name
        
        try:
            result = self.portfolio.generate_portfolio(output_path)
            
            self.assertEqual(result, output_path)
            self.assertTrue(os.path.exists(output_path))
            
            # 파일 내용 확인
            with open(output_path, 'r', encoding='utf-8') as f:
                content = f.read()
                self.assertIn(self.student_name, content)
                self.assertIn('AI 전문가 양성 과정 수료생', content)
                
        finally:
            if os.path.exists(output_path):
                os.unlink(output_path)


class TestIntegration(unittest.TestCase):
    """통합 테스트"""
    
    def setUp(self):
        """테스트 설정"""
        self.temp_dir = tempfile.mkdtemp()
        
    def tearDown(self):
        """테스트 정리"""
        shutil.rmtree(self.temp_dir, ignore_errors=True)
    
    def test_full_workflow(self):
        """전체 워크플로우 테스트"""
        # 1. 프로젝트 생성
        with patch('part_15_capstone_project.Path') as mock_path:
            mock_project_path = Path(self.temp_dir) / "test_project"
            mock_path.return_value = mock_project_path
            
            project = CapstoneProject("test_project", "ml")
            project_result = project.create_project_structure()
            
            self.assertTrue(project_result)
        
        # 2. 모델 평가
        evaluator = ModelEvaluator()
        
        # 테스트 데이터
        y_true = np.array([0, 1, 0, 1, 0])
        y_pred = np.array([0, 1, 0, 0, 1])
        
        with patch('sklearn.metrics') as mock_metrics:
            # Mock 설정
            mock_metrics.accuracy_score.return_value = 0.8
            mock_metrics.precision_score.return_value = 0.75
            mock_metrics.recall_score.return_value = 0.7
            mock_metrics.f1_score.return_value = 0.72
            
            result = evaluator.evaluate_classification(y_true, y_pred)
            
            # 결과 검증
            self.assertIn('accuracy', result)
            self.assertIn('precision', result)
            self.assertIn('recall', result)
            self.assertIn('f1_score', result)
        
        # 3. 포트폴리오 생성
        portfolio = PortfolioGenerator("테스트 학생")
        portfolio.add_skill("Python", "Advanced")
        portfolio.add_project({
            "name": "테스트 프로젝트",
            "description": "테스트 설명",
            "technologies": "Python, TensorFlow",
            "results": "정확도 90%"
        })
        
        with tempfile.NamedTemporaryFile(mode='w', suffix='.html', delete=False) as f:
            output_path = f.name
        
        try:
            result = portfolio.generate_portfolio(output_path)
            
            # 결과 검증
            self.assertNotEqual(result, "")
            if result:
                self.assertTrue(os.path.exists(result))
                
        finally:
            if os.path.exists(output_path):
                os.unlink(output_path)


class TestErrorHandling(unittest.TestCase):
    """에러 처리 테스트"""
    
    def test_capstone_project_invalid_type(self):
        """잘못된 프로젝트 타입 테스트"""
        project = CapstoneProject("test", "invalid_type")
        
        # 잘못된 타입이어도 초기화는 성공해야 함
        self.assertEqual(project.project_type, "invalid_type")
    
    def test_portfolio_generator_invalid_project_info(self):
        """잘못된 프로젝트 정보 테스트"""
        portfolio = PortfolioGenerator("테스트")
        
        # None 값 추가 시도
        portfolio.add_project(None)
        
        # None이 추가되어도 에러가 발생하지 않아야 함
        self.assertEqual(len(portfolio.projects), 1)
        self.assertIsNone(portfolio.projects[0])


if __name__ == '__main__':
    # 테스트 실행
    unittest.main(verbosity=2) 