import unittest
import numpy as np
import os
import sys

# Add the paths to sys.path
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../photo-quality-analyzer')))

from photo_quality_analyzer_core.analyzer import (
    _calculate_sharpness, 
    _calculate_exposure, 
    _calculate_dynamic_range, 
    _calculate_focus_area,
    SENSOR_SIZES,
    CAMERA_DYNAMIC_RANGE
)
import photo_quality_analyzer_core.analyzer as analyzer

class TestPhase2Metrics(unittest.TestCase):
    """Test suite for Phase 2 Context-Aware Metrics."""

    def setUp(self):
        # Create a clean image (100x100)
        self.img = np.zeros((100, 100, 3), dtype=np.uint8)
        self.gray = np.zeros((100, 100), dtype=np.uint8)
        
        # Add a gradient to ensure non-zero Histogram Width for Dynamic Range testing
        # 0 to 255 gradient across the x-axis
        for x in range(100):
            val = int((x / 100.0) * 255)
            self.gray[:, x] = val
            
        # Add high-frequency texture but LOW intensity
        # Checkerboard pattern in top-left (Mock ROI)
        self.gray[0:2, 0:2] = 1 
        # Texture for sharpness - just a few pixels
        self.gray[50:52, 50:52] = 128 # Stronger edge for sharpness
        
        # Sync RGB
        for c in range(3):
            self.img[:, :, c] = self.gray

    def test_sharpness_aperture_adjustment(self):
        """Test that sharpness score is adjusted based on aperture."""
        # 1. Base case: f/8 (optimal)
        metadata_f8 = {"aperture": 8.0}
        score_f8, expl_f8 = _calculate_sharpness(self.gray, metadata_f8)
        
        # 2. Diffraction-limited: f/22
        metadata_f22 = {"aperture": 22.0}
        score_f22, expl_f22 = _calculate_sharpness(self.gray, metadata_f22)
        
        print(f"\nSharpness context test:")
        print(f"f/8 score: {score_f8:.4f}, Explanation: {expl_f8}")
        print(f"f/22 score: {score_f22:.4f}, Explanation: {expl_f22}")
        
        self.assertIn("Diffraction Limit", expl_f22)
        # We removed the artificial boost, so we don't assert score_22 > score_8 anymore.
        # Instead, we verify we get the warning.
        
    def test_exposure_action_tolerance(self):
        """Test that exposure scoring is more lenient for fast shutter speeds."""
        # Create a slightly clipped image (4% highlight clipping)
        # highlight_tolerance is 0.02 for general, 0.05 for action
        clipped_gray = np.zeros((100, 100), dtype=np.uint8) + 128
        clipped_gray[:20, :20] = 255 # 400 pixels = 4% (0.04)
        
        # 1. Slow shutter speed (1/50s) -> Tolerance 0.02 -> Penalty applies
        metadata_slow = {"shutter_speed": 0.02}
        score_slow, expl_slow = _calculate_exposure(clipped_gray, metadata_slow)
        
        # 2. Fast shutter speed (1/1000s) -> Tolerance 0.05 -> Penalty is 0
        metadata_fast = {"shutter_speed": 0.001}
        score_fast, expl_fast = _calculate_exposure(clipped_gray, metadata_fast)
        
        print(f"\nExposure context test:")
        print(f"1/50s score: {score_slow:.4f}, Explanation: {expl_slow}")
        print(f"1/1000s score: {score_fast:.4f}, Explanation: {expl_fast}")
        
        # Fast shutter should be more lenient
        self.assertGreater(score_fast, score_slow)
        self.assertIn("action", expl_fast.lower())

    def test_dynamic_range_camera_baseline(self):
        """Test that DR scoring accounts for camera capabilities."""
        # 1. Low-end camera
        metadata_low = {"camera_model": "Sony RX100 VII"} # 11 stops
        score_low, _ = _calculate_dynamic_range(self.gray, metadata_low)
        
        # 2. High-end camera
        metadata_high = {"camera_model": "Sony A7R V"} # 14.8 stops
        score_high, _ = _calculate_dynamic_range(self.gray, metadata_high)
        
        print(f"\nDR context test:")
        print(f"RX100 score: {score_low:.4f}")
        print(f"A7RV score: {score_high:.4f}")
        
        # Lower capability camera gets a HIGHER score for the same content (gradient)
        # because it's utilizing more of its available potential.
        self.assertGreater(score_low, score_high)

    def test_focus_dof_awareness(self):
        """Test that focus scoring is more lenient for shallow DOF."""
        # Mock YOLO model presence to enter the logic block
        original_model = analyzer.g_yolo_model
        
        class MockInput:
            def __init__(self, name):
                self.name = name

        class MockModel:
            def get_inputs(self):
                return [MockInput("images")]
            def get_modelmeta(self):
                class Meta:
                    custom_metadata_map = {'names': "{0: 'person'}"}
                return Meta()
            def run(self, output_names, input_feed):
                # Return mock YOLO26 output (1, 300, 6)
                # [x1, y1, x2, y2, score, class]
                output = np.zeros((1, 300, 6), dtype=np.float32)
                # Set box for person at index 0 (x1, y1, x2, y2, score, class)
                # Input size is 640x640
                output[0, 0, :] = [100, 100, 200, 200, 0.9, 0]
                return [output]

        analyzer.g_yolo_model = MockModel()
        analyzer.g_coco_names = ["person"]
        
        try:
            base_score = 0.5
            
            # 1. Deep DOF (f/16, wide angle)
            metadata_deep = {"aperture": 16.0, "focal_length": 24.0}
            score_deep, expl_deep, _, _ = _calculate_focus_area(self.img, self.gray, base_score, metadata_deep)
            
            # 2. Shallow DOF (f/1.4, portrait)
            metadata_shallow = {"aperture": 1.4, "focal_length": 85.0}
            score_shallow, expl_shallow, _, _ = _calculate_focus_area(self.img, self.gray, base_score, metadata_shallow)
            
            print(f"\nFocus DOF test:")
            print(f"Deep DOF score: {score_deep:.4f}, Explanation: {expl_deep}")
            print(f"Shallow DOF score: {score_shallow:.4f}, Explanation: {expl_shallow}")
            
            # Shallow DOF should be more lenient
            self.assertGreater(score_shallow, score_deep)
            # New multi-subject logic uses different explanation format
            self.assertIn("soft", expl_shallow.lower())


        finally:
            analyzer.g_yolo_model = original_model

if __name__ == '__main__':
    unittest.main()
