"""
Part 10: AI 전문가 경로 실습 테스트
"""

import unittest
import sys
import os
import numpy as np
from unittest.mock import patch, MagicMock

# 상위 디렉토리를 Python 경로에 추가
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

# Part 10 모듈 import
try:
    import part_10_expert_path as part10
except ImportError as e:
    print(f"Warning: part_10_expert_path 모듈을 import할 수 없습니다: {e}")
    part10 = None


class TestPart10ExpertAIAnalyst(unittest.TestCase):
    """Part 10 AI 전문가 분석가 테스트 클래스"""

    def setUp(self):
        """테스트 설정"""
        if part10 is None:
            self.skipTest("part_10_expert_path 모듈을 import할 수 없습니다.")
        
        self.analyst = part10.ExpertAIAnalyst("테스트AI", "데이터 사이언티스트")

    def test_analyst_initialization(self):
        """분석가 초기화 테스트"""
        self.assertEqual(self.analyst.name, "테스트AI")
        self.assertEqual(self.analyst.specialization, "데이터 사이언티스트")
        self.assertEqual(len(self.analyst.skills), 0)
        self.assertEqual(len(self.analyst.projects), 0)

    def test_add_skill(self):
        """기술 추가 테스트"""
        initial_skills_count = len(self.analyst.skills)
        
        with patch('builtins.print') as mock_print:
            self.analyst.add_skill("Python")
        
        self.assertEqual(len(self.analyst.skills), initial_skills_count + 1)
        self.assertIn("Python", self.analyst.skills)
        
        # print 호출 확인
        mock_print.assert_called_once_with("기술 추가: Python")

    def test_add_project(self):
        """프로젝트 추가 테스트"""
        initial_projects_count = len(self.analyst.projects)
        
        with patch('builtins.print') as mock_print:
            self.analyst.add_project(
                "테스트 프로젝트",
                "테스트 설명",
                ["Python", "Pandas"]
            )
        
        self.assertEqual(len(self.analyst.projects), initial_projects_count + 1)
        
        project = self.analyst.projects[0]
        self.assertEqual(project['name'], "테스트 프로젝트")
        self.assertEqual(project['description'], "테스트 설명")
        self.assertEqual(project['technologies'], ["Python", "Pandas"])
        self.assertEqual(project['status'], "completed")
        
        # print 호출 확인
        mock_print.assert_called_once_with("프로젝트 추가: 테스트 프로젝트")

    def test_get_expertise_summary(self):
        """전문성 요약 테스트"""
        # 기술과 프로젝트 추가
        self.analyst.add_skill("Python")
        self.analyst.add_skill("Pandas")
        self.analyst.add_project("프로젝트1", "설명1", ["Python"])
        self.analyst.add_project("프로젝트2", "설명2", ["Pandas"])
        
        summary = self.analyst.get_expertise_summary()
        
        self.assertEqual(summary['name'], "테스트AI")
        self.assertEqual(summary['specialization'], "데이터 사이언티스트")
        self.assertEqual(summary['total_skills'], 2)
        self.assertEqual(summary['total_projects'], 2)
        self.assertEqual(summary['skills'], ["Python", "Pandas"])
        self.assertEqual(summary['projects'], ["프로젝트1", "프로젝트2"])


class TestPart10AdvancedMLPipeline(unittest.TestCase):
    """Part 10 고급 ML 파이프라인 테스트 클래스"""

    def setUp(self):
        """테스트 설정"""
        if part10 is None:
            self.skipTest("part_10_expert_path 모듈을 import할 수 없습니다.")
        
        self.pipeline = part10.AdvancedMLPipeline()

    def test_create_synthetic_data(self):
        """합성 데이터 생성 테스트"""
        X, y = self.pipeline.create_synthetic_data(n_samples=100, n_features=5)
        
        self.assertEqual(X.shape, (100, 5))
        self.assertEqual(y.shape, (100,))
        self.assertTrue(np.all(np.isin(y, [0, 1])))  # 이진 분류
        
        # 결정 경계 확인 (X[:, 0] + X[:, 1] > 0)
        expected_y = (X[:, 0] + X[:, 1] > 0).astype(int)
        np.testing.assert_array_equal(y, expected_y)

    @patch('part_10_expert_path.train_test_split')
    @patch('part_10_expert_path.cross_val_score')
    def test_train_model(self, mock_cv_score, mock_train_test_split):
        """모델 훈련 테스트"""
        # Mock 설정
        X = np.random.randn(100, 5)
        y = np.random.randint(0, 2, 100)
        
        mock_train_test_split.return_value = (X[:80], X[80:], y[:80], y[80:])
        mock_cv_score.return_value = np.array([0.8, 0.85, 0.9, 0.75, 0.8])
        
        # Mock 모델
        mock_model = MagicMock()
        mock_model.score.return_value = 0.85
        mock_model.feature_importances_ = np.array([0.1, 0.2, 0.3, 0.2, 0.2])
        
        with patch('part_10_expert_path.RandomForestClassifier', return_value=mock_model):
            model, metrics = self.pipeline.train_model(X, y, "random_forest")
        
        # 결과 검증
        self.assertIn("random_forest", self.pipeline.models)
        self.assertIn("random_forest", self.pipeline.performance_metrics)
        self.assertIn("random_forest", self.pipeline.feature_importance)
        
        # 메트릭 검증
        self.assertEqual(metrics['train_score'], 0.85)
        self.assertEqual(metrics['test_score'], 0.85)
        self.assertEqual(metrics['cv_mean'], 0.82)
        self.assertAlmostEqual(metrics['cv_std'], 0.05, places=2)

    def test_analyze_performance(self):
        """성능 분석 테스트"""
        # 성능 메트릭 설정
        self.pipeline.performance_metrics["test_model"] = {
            'train_score': 0.9,
            'test_score': 0.85,
            'cv_mean': 0.87,
            'cv_std': 0.03
        }
        
        with patch('builtins.print') as mock_print:
            result = self.pipeline.analyze_performance("test_model")
        
        # 결과 검증
        self.assertEqual(result['train_score'], 0.9)
        self.assertEqual(result['test_score'], 0.85)
        
        # print 호출 확인
        print_calls = [call[0][0] for call in mock_print.call_args_list]
        self.assertTrue(any("test_model 성능 분석" in str(call) for call in print_calls))

    def test_analyze_performance_nonexistent_model(self):
        """존재하지 않는 모델 성능 분석 테스트"""
        with patch('builtins.print') as mock_print:
            result = self.pipeline.analyze_performance("nonexistent_model")
        
        self.assertIsNone(result)
        mock_print.assert_called_once_with("모델 nonexistent_model이 훈련되지 않았습니다.")


class TestPart10ExpertPathGuide(unittest.TestCase):
    """Part 10 전문가 경로 가이드 테스트 클래스"""

    def setUp(self):
        """테스트 설정"""
        if part10 is None:
            self.skipTest("part_10_expert_path 모듈을 import할 수 없습니다.")
        
        self.guide = part10.ExpertPathGuide()

    def test_career_paths_initialization(self):
        """경력 경로 초기화 테스트"""
        expected_paths = ['ML_Engineer', 'Data_Scientist', 'AI_Researcher', 'MLOps_Engineer']
        
        for path in expected_paths:
            self.assertIn(path, self.guide.career_paths)
            
        # 각 경로에 필수 필드가 있는지 확인
        for path_name, path_info in self.guide.career_paths.items():
            self.assertIn('description', path_info)
            self.assertIn('skills', path_info)
            self.assertIn('projects', path_info)
            self.assertIn('salary_range', path_info)

    def test_get_career_path(self):
        """경력 경로 조회 테스트"""
        # 존재하는 경로
        ml_engineer = self.guide.get_career_path('ML_Engineer')
        self.assertIsNotNone(ml_engineer)
        self.assertEqual(ml_engineer['description'], '머신러닝 모델 개발 및 배포')
        self.assertIn('Python', ml_engineer['skills'])
        
        # 존재하지 않는 경로
        with patch('builtins.print') as mock_print:
            result = self.guide.get_career_path('NonexistentPath')
        
        self.assertIsNone(result)
        mock_print.assert_called_once_with("경력 경로 'NonexistentPath'을 찾을 수 없습니다.")

    def test_recommend_path(self):
        """경력 경로 추천 테스트"""
        interests = ["머신러닝", "자동화"]
        current_skills = ["Python", "Docker"]
        
        with patch('builtins.print') as mock_print:
            recommendations = self.guide.recommend_path(interests, current_skills)
        
        # 추천 결과가 점수 순으로 정렬되어 있는지 확인
        scores = [score for _, score in recommendations]
        self.assertEqual(scores, sorted(scores, reverse=True))
        
        # print 호출 확인 (추천 메시지가 출력되었는지)
        print_calls = [call[0][0] for call in mock_print.call_args_list]
        self.assertTrue(any("경력 경로 추천" in str(call) for call in print_calls))

    def test_recommend_path_scoring(self):
        """경력 경로 점수 계산 테스트"""
        # ML_Engineer 경로에 최적화된 입력
        interests = ["머신러닝", "배포"]
        current_skills = ["Python", "Docker", "Kubernetes"]
        
        recommendations = self.guide.recommend_path(interests, current_skills)
        
        # ML_Engineer가 높은 점수를 받아야 함
        ml_engineer_score = next(score for path, score in recommendations if path == 'ML_Engineer')
        self.assertGreater(ml_engineer_score, 0)


class TestPart10Integration(unittest.TestCase):
    """Part 10 통합 테스트"""

    def test_full_expert_workflow(self):
        """전체 전문가 워크플로우 테스트"""
        if part10 is None:
            self.skipTest("part_10_expert_path 모듈을 import할 수 없습니다.")
        
        # 1. 전문가 분석가 생성 및 설정
        analyst = part10.ExpertAIAnalyst("통합테스트", "ML 엔지니어")
        analyst.add_skill("Python")
        analyst.add_skill("TensorFlow")
        analyst.add_project("통합 프로젝트", "테스트 설명", ["Python", "TensorFlow"])
        
        summary = analyst.get_expertise_summary()
        self.assertEqual(summary['total_skills'], 2)
        self.assertEqual(summary['total_projects'], 1)
        
        # 2. ML 파이프라인 실행
        pipeline = part10.AdvancedMLPipeline()
        X, y = pipeline.create_synthetic_data(n_samples=50, n_features=3)
        
        # Mock을 사용하여 모델 훈련 시뮬레이션
        with patch('part_10_expert_path.train_test_split') as mock_split:
            with patch('part_10_expert_path.cross_val_score') as mock_cv:
                mock_split.return_value = (X[:40], X[40:], y[:40], y[40:])
                mock_cv.return_value = np.array([0.8, 0.85, 0.9])
                
                mock_model = MagicMock()
                mock_model.score.return_value = 0.85
                mock_model.feature_importances_ = np.array([0.3, 0.4, 0.3])
                
                with patch('part_10_expert_path.RandomForestClassifier', return_value=mock_model):
                    model, metrics = pipeline.train_model(X, y, "test_model")
        
        self.assertIn("test_model", pipeline.models)
        self.assertEqual(metrics['train_score'], 0.85)
        
        # 3. 경력 경로 추천
        guide = part10.ExpertPathGuide()
        recommendations = guide.recommend_path(["머신러닝"], ["Python"])
        
        self.assertIsInstance(recommendations, list)
        self.assertGreater(len(recommendations), 0)

    def test_career_path_matching(self):
        """경력 경로 매칭 테스트"""
        if part10 is None:
            self.skipTest("part_10_expert_path 모듈을 import할 수 없습니다.")
        
        guide = part10.ExpertPathGuide()
        
        # Data Scientist에 최적화된 프로필
        data_scientist_interests = ["데이터 분석", "통계"]
        data_scientist_skills = ["Python", "Pandas", "SQL"]
        
        recommendations = guide.recommend_path(data_scientist_interests, data_scientist_skills)
        
        # Data_Scientist가 상위에 있어야 함
        top_paths = [path for path, _ in recommendations[:2]]
        self.assertIn('Data_Scientist', top_paths)


class TestPart10ErrorHandling(unittest.TestCase):
    """Part 10 에러 처리 테스트"""

    def test_pipeline_error_handling(self):
        """파이프라인 에러 처리 테스트"""
        if part10 is None:
            self.skipTest("part_10_expert_path 모듈을 import할 수 없습니다.")
        
        pipeline = part10.AdvancedMLPipeline()
        
        # 존재하지 않는 모델 분석
        with patch('builtins.print') as mock_print:
            result = pipeline.analyze_performance("nonexistent")
        
        self.assertIsNone(result)
        mock_print.assert_called_once_with("모델 nonexistent이 훈련되지 않았습니다.")

    def test_guide_error_handling(self):
        """가이드 에러 처리 테스트"""
        if part10 is None:
            self.skipTest("part_10_expert_path 모듈을 import할 수 없습니다.")
        
        guide = part10.ExpertPathGuide()
        
        # 존재하지 않는 경력 경로 조회
        with patch('builtins.print') as mock_print:
            result = guide.get_career_path("InvalidPath")
        
        self.assertIsNone(result)
        mock_print.assert_called_once_with("경력 경로 'InvalidPath'을 찾을 수 없습니다.")


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