"""
자동화된 테스트 시스템
단위 테스트, 통합 테스트, 성능 테스트를 포함합니다.
"""

import unittest
import numpy as np
import tempfile
import os
import sys
import time
import logging
from typing import Dict, List, Any

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

from src.evaluator import Evaluator
from src.utils.reference_extractor import ReferenceDataExtractor
from src.metrics.empir3d_evaluator import Empir3DEvaluator
from src.metrics.deep_emd_calculator import DeepEMDCalculator
from src.renderer.improved_renderer import ImprovedRenderer
from src.generators.scene_dreamer import SceneDreamer
from src.integration.scene_detection import SceneDetectionSystem

logger = logging.getLogger(__name__)

class TestDataGenerator:
    """테스트 데이터 생성기"""
    
    @staticmethod
    def generate_test_3d_model(num_vertices: int = 100) -> Dict:
        """테스트용 3D 모델 생성"""
        vertices = []
        for _ in range(num_vertices):
            vertex = [
                np.random.uniform(-1, 1),
                np.random.uniform(-1, 1),
                np.random.uniform(-1, 1)
            ]
            vertices.append(vertex)
        
        # 간단한 면 생성
        faces = []
        for i in range(0, num_vertices - 2, 3):
            faces.append([i, i + 1, i + 2])
        
        return {
            'vertices': vertices,
            'faces': faces,
            'center': [0.0, 0.0, 0.0],
            'scale': [1.0, 1.0, 1.0]
        }
    
    @staticmethod
    def generate_test_image(width: int = 512, height: int = 512) -> np.ndarray:
        """테스트용 이미지 생성"""
        image = np.random.randint(0, 255, (height, width, 3), dtype=np.uint8)
        return image
    
    @staticmethod
    def generate_test_reference_data() -> Dict:
        """테스트용 참조 데이터 생성"""
        return {
            'objects': [
                {
                    'bbox': [100, 100, 200, 200],
                    'confidence': 0.9,
                    'class': 0
                }
            ],
            'bounding_boxes': [[100, 100, 200, 200]],
            'class_labels': [0],
            'confidence_scores': [0.9]
        }


class UnitTests(unittest.TestCase):
    """단위 테스트"""
    
    def setUp(self):
        """테스트 설정"""
        self.test_data_generator = TestDataGenerator()
        self.temp_dir = tempfile.mkdtemp()
    
    def tearDown(self):
        """테스트 정리"""
        import shutil
        shutil.rmtree(self.temp_dir, ignore_errors=True)
    
    def test_reference_extractor_initialization(self):
        """참조 데이터 추출기 초기화 테스트"""
        extractor = ReferenceDataExtractor()
        self.assertIsNotNone(extractor)
        self.assertIsNotNone(extractor.detector)
    
    def test_ground_truth_creation(self):
        """Ground Truth 생성 테스트"""
        extractor = ReferenceDataExtractor()
        test_image = self.test_data_generator.generate_test_image()
        
        # 임시 이미지 파일 생성
        temp_image_path = os.path.join(self.temp_dir, 'test_image.jpg')
        import cv2
        cv2.imwrite(temp_image_path, test_image)
        
        # Ground Truth 생성
        ground_truth = extractor.create_unified_ground_truth(temp_image_path)
        
        # 검증
        self.assertIn('vertices', ground_truth)
        self.assertIn('faces', ground_truth)
        self.assertIn('center', ground_truth)
        self.assertIn('scale', ground_truth)
        self.assertIsInstance(ground_truth['vertices'], list)
        self.assertIsInstance(ground_truth['faces'], list)
    
    def test_empir3d_evaluator_initialization(self):
        """Empir3D 평가기 초기화 테스트"""
        evaluator = Empir3DEvaluator()
        self.assertIsNotNone(evaluator)
        self.assertIsNotNone(evaluator.resolution_calculator)
        self.assertIsNotNone(evaluator.accuracy_calculator)
        self.assertIsNotNone(evaluator.coverage_calculator)
        self.assertIsNotNone(evaluator.artifact_calculator)
    
    def test_empir3d_evaluation(self):
        """Empir3D 평가 테스트"""
        evaluator = Empir3DEvaluator()
        model_3d = self.test_data_generator.generate_test_3d_model()
        ground_truth_3d = self.test_data_generator.generate_test_3d_model()
        
        result = evaluator.evaluate_3d_model(model_3d, ground_truth_3d)
        
        # 검증
        self.assertIn('empir3d_score', result)
        self.assertIn('resolution', result)
        self.assertIn('accuracy', result)
        self.assertIn('coverage', result)
        self.assertIn('artifact', result)
        self.assertIsInstance(result['empir3d_score'], (int, float))
    
    def test_deep_emd_calculator_initialization(self):
        """DeepEMD 계산기 초기화 테스트"""
        calculator = DeepEMDCalculator()
        self.assertIsNotNone(calculator)
        self.assertIsNotNone(calculator.transformer_model)
    
    def test_improved_renderer_initialization(self):
        """개선된 렌더러 초기화 테스트"""
        renderer = ImprovedRenderer()
        self.assertIsNotNone(renderer)
        self.assertIsNotNone(renderer.quality_enhancer)
        self.assertIsNotNone(renderer.edge_detector)
    
    def test_scene_dreamer_initialization(self):
        """SceneDreamer 초기화 테스트"""
        dreamer = SceneDreamer()
        self.assertIsNotNone(dreamer)
        self.assertIsNotNone(dreamer.scene_encoder)
        self.assertIsNotNone(dreamer.3d_generator)
        self.assertIsNotNone(dreamer.view_synthesizer)
    
    def test_scene_detection_system_initialization(self):
        """장면 탐지 시스템 초기화 테스트"""
        detector = SceneDetectionSystem()
        self.assertIsNotNone(detector)
        self.assertIsNotNone(detector.object_detector)
        self.assertIsNotNone(detector.scene_analyzer)
        self.assertIsNotNone(detector.spatial_analyzer)


class IntegrationTests(unittest.TestCase):
    """통합 테스트"""
    
    def setUp(self):
        """테스트 설정"""
        self.test_data_generator = TestDataGenerator()
        self.temp_dir = tempfile.mkdtemp()
    
    def tearDown(self):
        """테스트 정리"""
        import shutil
        shutil.rmtree(self.temp_dir, ignore_errors=True)
    
    def test_full_evaluation_pipeline(self):
        """전체 평가 파이프라인 테스트"""
        # 평가기 초기화
        evaluator = Evaluator()
        
        # 테스트 데이터 생성
        model_3d = self.test_data_generator.generate_test_3d_model()
        test_image = self.test_data_generator.generate_test_image()
        
        # 임시 파일 생성
        temp_model_path = os.path.join(self.temp_dir, 'test_model.glb')
        temp_image_path = os.path.join(self.temp_dir, 'test_image.jpg')
        
        # 모델 데이터 저장 (실제로는 GLB 파일이어야 함)
        import json
        with open(temp_model_path, 'w') as f:
            json.dump(model_3d, f)
        
        import cv2
        cv2.imwrite(temp_image_path, test_image)
        
        # 평가 실행
        try:
            result = evaluator.evaluate_model(temp_model_path, temp_image_path)
            
            # 검증
            self.assertIn('comprehensive_score', result)
            self.assertIn('grade', result)
            self.assertIn('metrics', result)
            self.assertIsInstance(result['comprehensive_score'], (int, float))
            self.assertIsInstance(result['grade'], str)
            
        except Exception as e:
            # 일부 의존성이 없을 수 있으므로 예외를 로깅만 함
            logger.warning(f"전체 평가 파이프라인 테스트에서 예외 발생: {str(e)}")
    
    def test_empir3d_integration(self):
        """Empir3D 통합 테스트"""
        evaluator = Empir3DEvaluator()
        model_3d = self.test_data_generator.generate_test_3d_model()
        ground_truth_3d = self.test_data_generator.generate_test_3d_model()
        
        result = evaluator.evaluate_3d_model(model_3d, ground_truth_3d)
        
        # 모든 지표가 계산되었는지 확인
        self.assertIn('empir3d_score', result)
        self.assertIn('resolution', result)
        self.assertIn('accuracy', result)
        self.assertIn('coverage', result)
        self.assertIn('artifact', result)
        
        # 각 지표의 점수가 유효한지 확인
        for metric in ['resolution', 'accuracy', 'coverage', 'artifact']:
            self.assertIn('score', result[metric])
            self.assertIsInstance(result[metric]['score'], (int, float))
            self.assertGreaterEqual(result[metric]['score'], 0.0)
            self.assertLessEqual(result[metric]['score'], 1.0)
    
    def test_scene_dreamer_integration(self):
        """SceneDreamer 통합 테스트"""
        dreamer = SceneDreamer()
        test_image = self.test_data_generator.generate_test_image()
        
        result = dreamer.generate_3d_scene(test_image, num_views=4)
        
        # 검증
        self.assertIn('3d_model', result)
        self.assertIn('multi_views', result)
        self.assertIn('scene_latent', result)
        self.assertIn('metadata', result)
        
        # 3D 모델 검증
        model_3d = result['3d_model']
        self.assertIn('vertices', model_3d)
        self.assertIn('faces', model_3d)
        self.assertIsInstance(model_3d['vertices'], list)
        self.assertIsInstance(model_3d['faces'], list)
    
    def test_scene_detection_integration(self):
        """장면 탐지 통합 테스트"""
        detector = SceneDetectionSystem()
        test_image = self.test_data_generator.generate_test_image()
        
        result = detector.detect_scene_objects(test_image)
        
        # 검증
        self.assertIn('detected_objects', result)
        self.assertIn('scene_analysis', result)
        self.assertIn('spatial_relations', result)
        self.assertIn('metadata', result)
        
        # 메타데이터 검증
        metadata = result['metadata']
        self.assertIn('num_objects', metadata)
        self.assertIn('scene_complexity', metadata)
        self.assertIn('detection_method', metadata)


class PerformanceTests(unittest.TestCase):
    """성능 테스트"""
    
    def setUp(self):
        """테스트 설정"""
        self.test_data_generator = TestDataGenerator()
    
    def test_evaluation_performance(self):
        """평가 성능 테스트"""
        evaluator = Empir3DEvaluator()
        model_3d = self.test_data_generator.generate_test_3d_model(1000)
        ground_truth_3d = self.test_data_generator.generate_test_3d_model(1000)
        
        # 성능 측정
        start_time = time.time()
        result = evaluator.evaluate_3d_model(model_3d, ground_truth_3d)
        end_time = time.time()
        
        execution_time = end_time - start_time
        
        # 성능 검증 (5초 이내 완료)
        self.assertLess(execution_time, 5.0)
        logger.info(f"Empir3D 평가 실행 시간: {execution_time:.2f}초")
    
    def test_rendering_performance(self):
        """렌더링 성능 테스트"""
        renderer = ImprovedRenderer()
        model_3d = self.test_data_generator.generate_test_3d_model(500)
        
        # 성능 측정
        start_time = time.time()
        rendered_images = renderer.render_multiple_views(model_3d, num_views=8)
        end_time = time.time()
        
        execution_time = end_time - start_time
        
        # 성능 검증 (10초 이내 완료)
        self.assertLess(execution_time, 10.0)
        logger.info(f"렌더링 실행 시간: {execution_time:.2f}초")
        logger.info(f"생성된 이미지 수: {len(rendered_images)}")
    
    def test_memory_usage(self):
        """메모리 사용량 테스트"""
        import psutil
        import os
        
        process = psutil.Process(os.getpid())
        initial_memory = process.memory_info().rss / 1024 / 1024  # MB
        
        # 대용량 데이터 처리
        evaluator = Empir3DEvaluator()
        model_3d = self.test_data_generator.generate_test_3d_model(5000)
        ground_truth_3d = self.test_data_generator.generate_test_3d_model(5000)
        
        result = evaluator.evaluate_3d_model(model_3d, ground_truth_3d)
        
        final_memory = process.memory_info().rss / 1024 / 1024  # MB
        memory_increase = final_memory - initial_memory
        
        # 메모리 사용량 검증 (500MB 이내 증가)
        self.assertLess(memory_increase, 500.0)
        logger.info(f"메모리 사용량 증가: {memory_increase:.2f}MB")


class SmokeTests(unittest.TestCase):
    """스모크 테스트"""
    
    def test_basic_functionality(self):
        """기본 기능 테스트"""
        # 모든 주요 컴포넌트가 초기화되는지 확인
        components = [
            ReferenceDataExtractor(),
            Empir3DEvaluator(),
            DeepEMDCalculator(),
            ImprovedRenderer(),
            SceneDreamer(),
            SceneDetectionSystem()
        ]
        
        for component in components:
            self.assertIsNotNone(component)
    
    def test_error_handling(self):
        """오류 처리 테스트"""
        evaluator = Empir3DEvaluator()
        
        # 잘못된 입력으로 테스트
        invalid_model = {}
        invalid_gt = {}
        
        result = evaluator.evaluate_3d_model(invalid_model, invalid_gt)
        
        # 오류가 발생해도 결과가 반환되는지 확인
        self.assertIsNotNone(result)
        self.assertIn('empir3d_score', result)
    
    def test_configuration_loading(self):
        """설정 로딩 테스트"""
        from config.evaluation_config import EVALUATION_CONFIG
        
        # 기본 설정이 로드되는지 확인
        self.assertIsNotNone(EVALUATION_CONFIG)
        self.assertIn('weights', EVALUATION_CONFIG)
        self.assertIn('thresholds', EVALUATION_CONFIG)


def run_all_tests():
    """모든 테스트 실행"""
    # 테스트 스위트 생성
    test_suite = unittest.TestSuite()
    
    # 테스트 클래스 추가
    test_classes = [
        UnitTests,
        IntegrationTests,
        PerformanceTests,
        SmokeTests
    ]
    
    for test_class in test_classes:
        tests = unittest.TestLoader().loadTestsFromTestCase(test_class)
        test_suite.addTests(tests)
    
    # 테스트 실행
    runner = unittest.TextTestRunner(verbosity=2)
    result = runner.run(test_suite)
    
    return result.wasSuccessful()


if __name__ == '__main__':
    # 로깅 설정
    logging.basicConfig(
        level=logging.INFO,
        format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
    )
    
    # 모든 테스트 실행
    success = run_all_tests()
    
    if success:
        print("\n✅ 모든 테스트가 성공적으로 완료되었습니다!")
        sys.exit(0)
    else:
        print("\n❌ 일부 테스트가 실패했습니다.")
        sys.exit(1)
