import numpy as np
import pytest
from PIL import Image

from app.qt.image_processor import Stats, _rgb_to_hsv_numpy, QtImageProcessor


def test_stats_summary():
    s = Stats(
        matches_all=50, total_all=100,
        matches_keep=40, total_keep=80,
        matches_excl=10, total_excl=20
    )
    
    # Mock translator
    def mock_t(key, **kwargs):
        if key == "stats.placeholder":
            return "Placeholder"
        return f"{kwargs['with_pct']:.1f} {kwargs['without_pct']:.1f} {kwargs['excluded_pct']:.1f} {kwargs['excluded_match_pct']:.1f}"

    res = s.summary(mock_t)
    # with_pct: 40/80 = 50.0
    # without_pct: 50/100 = 50.0
    # excluded_pct: 20/100 = 20.0
    # excluded_match_pct: 10/20 = 50.0
    assert res == "50.0 50.0 20.0 50.0"

def test_stats_empty():
    s = Stats()
    assert s.summary(lambda k, **kw: "Empty") == "Empty"


def test_rgb_to_hsv_numpy():
    # Test red
    arr = np.array([[[1.0, 0.0, 0.0]]], dtype=np.float32)
    hsv = _rgb_to_hsv_numpy(arr)
    assert np.allclose(hsv[0, 0], [0.0, 100.0, 100.0])

    # Test green
    arr = np.array([[[0.0, 1.0, 0.0]]], dtype=np.float32)
    hsv = _rgb_to_hsv_numpy(arr)
    assert np.allclose(hsv[0, 0], [120.0, 100.0, 100.0])

    # Test blue
    arr = np.array([[[0.0, 0.0, 1.0]]], dtype=np.float32)
    hsv = _rgb_to_hsv_numpy(arr)
    assert np.allclose(hsv[0, 0], [240.0, 100.0, 100.0])

    # Test white
    arr = np.array([[[1.0, 1.0, 1.0]]], dtype=np.float32)
    hsv = _rgb_to_hsv_numpy(arr)
    assert np.allclose(hsv[0, 0], [0.0, 0.0, 100.0])

    # Test black
    arr = np.array([[[0.0, 0.0, 0.0]]], dtype=np.float32)
    hsv = _rgb_to_hsv_numpy(arr)
    assert np.allclose(hsv[0, 0], [0.0, 0.0, 0.0])


def test_qt_processor_matches_legacy():
    proc = QtImageProcessor()
    proc.hue_min = 350
    proc.hue_max = 10
    proc.sat_min = 50
    proc.val_min = 50
    proc.val_max = 100

    # Red wraps around 360, so H=0 -> ok
    assert proc._matches(255, 0, 0) is True
    # Green H=120 -> fail
    assert proc._matches(0, 255, 0) is False
    # Dark red S=100, V=25 -> fail because val_min=50
    assert proc._matches(64, 0, 0) is False

def test_set_overlay_color():
    proc = QtImageProcessor()
    # default red
    assert proc.overlay_r == 255
    assert proc.overlay_g == 0
    assert proc.overlay_b == 0

    proc.set_overlay_color("#00ff00")
    assert proc.overlay_r == 0
    assert proc.overlay_g == 255
    assert proc.overlay_b == 0

    # invalid hex does nothing
    proc.set_overlay_color("blue")
    assert proc.overlay_r == 0

def test_coordinate_scaling():
    proc = QtImageProcessor()
    
    # Create a 200x200 image where everything is red
    red_img_small = Image.new("RGBA", (200, 200), (255, 0, 0, 255))
    proc.orig_img = red_img_small # satisfy preview logic
    proc.preview_img = red_img_small 
    
    # All red. Thresholds cover all red.
    proc.hue_min = 0
    proc.hue_max = 360
    proc.sat_min = 10
    proc.val_min = 10
    
    # Exclude the right half (100-200)
    proc.set_exclusions([{"kind": "rect", "coords": (100, 0, 200, 200)}])
    
    # Verify small stats
    s_small = proc.get_stats_headless(red_img_small)
    # total=40000, keep=20000, excl=20000
    assert s_small.total_all == 40000
    assert s_small.total_keep == 20000
    assert s_small.total_excl == 20000
    
    # Now check on a 1000x1000 image (5x scale)
    red_img_large = Image.new("RGBA", (1000, 1000), (255, 0, 0, 255))
    s_large = proc.get_stats_headless(red_img_large)
    
    # total=1,000,000. If scaling works, keep=500,000, excl=500,000.
    # If scaling FAILED, the mask is still 100x200 (20,000 px) -> excl=20,000.
    assert s_large.total_all == 1000000
    assert s_large.total_keep == 500000
    assert s_large.total_excl == 500000
