"""uniformity.py の仕様ベーステスト．

仕様出典: MiniTias-Evaluation/SPEC_02_照明均一性評価アルゴリズム.md
"""

import numpy
import pytest

from sm_tias_lightsim.uniformity import (
    REC709_COEFF_B,
    REC709_COEFF_G,
    REC709_COEFF_R,
    calc_uniformity,
    to_grayscale,
)

# ---------------------------------------------------------------------------
# REC709 定数のテスト
# ---------------------------------------------------------------------------


def test_rec709_coefficients_sum_to_one():
    """REC709 3 係数の和が 1.0 であること．

    仕様: Y = 0.2126 R + 0.7152 G + 0.0722 B (SPEC_02 輝度変換節)
    """
    total = REC709_COEFF_R + REC709_COEFF_G + REC709_COEFF_B
    assert abs(total - 1.0) < 1e-10


def test_rec709_coefficient_r_value():
    """REC709 R 係数が 0.2126 であること."""
    assert REC709_COEFF_R == pytest.approx(0.2126)


def test_rec709_coefficient_g_value():
    """REC709 G 係数が 0.7152 であること."""
    assert REC709_COEFF_G == pytest.approx(0.7152)


def test_rec709_coefficient_b_value():
    """REC709 B 係数が 0.0722 であること."""
    assert REC709_COEFF_B == pytest.approx(0.0722)


# ---------------------------------------------------------------------------
# to_grayscale のテスト
# ---------------------------------------------------------------------------


def test_grayscale_uses_rec709_coefficients_red(red_pixel):
    """純赤入力で期待輝度が REC709_COEFF_R * 255 になること．

    仕様: Y = 0.2126 R + 0.7152 G + 0.0722 B (SPEC_02)
    """
    result = to_grayscale(red_pixel)
    expected = REC709_COEFF_R * 255.0
    assert result[0, 0] == pytest.approx(expected)


def test_grayscale_uses_rec709_coefficients_green(green_pixel):
    """純緑入力で期待輝度が REC709_COEFF_G * 255 になること."""
    result = to_grayscale(green_pixel)
    expected = REC709_COEFF_G * 255.0
    assert result[0, 0] == pytest.approx(expected)


def test_grayscale_uses_rec709_coefficients_blue(blue_pixel):
    """純青入力で期待輝度が REC709_COEFF_B * 255 になること."""
    result = to_grayscale(blue_pixel)
    expected = REC709_COEFF_B * 255.0
    assert result[0, 0] == pytest.approx(expected)


def test_grayscale_returns_float64(red_pixel):
    """to_grayscale の出力 dtype が numpy.float64 であること．

    仕様: 出力値域 0.0〜255.0, float64 (SPEC_02 輝度変換節)
    """
    result = to_grayscale(red_pixel)
    assert result.dtype == numpy.float64


def test_grayscale_preserves_shape_hxw():
    """入力 (H, W, 3) に対して出力が (H, W) になること．

    仕様: 出力は H x W の輝度画像 (SPEC_02)
    """
    image = numpy.zeros((7, 13, 3), dtype=numpy.uint8)
    result = to_grayscale(image)
    assert result.shape == (7, 13)


def test_grayscale_white_pixel_equals_255():
    """白 (255, 255, 255) の輝度が 255 になること．

    Rec.709 係数の和が 1.0 なので白は 255 のまま．
    """
    white = numpy.array([[[255, 255, 255]]], dtype=numpy.uint8)
    result = to_grayscale(white)
    assert result[0, 0] == pytest.approx(255.0)


def test_grayscale_black_pixel_equals_zero():
    """黒 (0, 0, 0) の輝度が 0 になること."""
    black = numpy.array([[[0, 0, 0]]], dtype=numpy.uint8)
    result = to_grayscale(black)
    assert result[0, 0] == pytest.approx(0.0)


# ---------------------------------------------------------------------------
# calc_uniformity のテスト
# ---------------------------------------------------------------------------


def test_calc_uniformity_uniform_image(uniform_luminance_100x100):
    """全画素が同値の輝度マップでは cov==0, std==0, max_min_ratio==1.0 になること．

    仕様:
    - CoV = σ / μ (SPEC_02 均一性指標節)
    - Max/Min Ratio = max / min; 理想的な均一照明では 1.0 (SPEC_02)
    """
    result = calc_uniformity(uniform_luminance_100x100)
    assert result["cov"] == pytest.approx(0.0)
    assert result["std"] == pytest.approx(0.0)
    assert result["max_min_ratio"] == pytest.approx(1.0)
    assert result["mean"] == pytest.approx(100.0)
    assert result["max"] == pytest.approx(100.0)
    assert result["min"] == pytest.approx(100.0)


def test_calc_uniformity_known_values():
    """既知の配列 [50, 100, 150] で mean/std/cov が正しい値になること．

    手計算:
    - mean = 100.0
    - std  = sqrt(((50-100)^2 + (100-100)^2 + (150-100)^2) / 3)
           = sqrt((2500 + 0 + 2500) / 3) = sqrt(5000/3) ≈ 40.8248...
    - cov  = std / mean ≈ 0.40825...
    - max_min_ratio = 150 / 50 = 3.0
    """
    luminance = numpy.array([[50.0, 100.0, 150.0]], dtype=numpy.float64)
    result = calc_uniformity(luminance)

    expected_mean = 100.0
    expected_std = numpy.std(numpy.array([50.0, 100.0, 150.0]))
    expected_cov = expected_std / expected_mean
    expected_ratio = 150.0 / 50.0

    assert result["mean"] == pytest.approx(expected_mean)
    assert result["std"] == pytest.approx(float(expected_std))
    assert result["cov"] == pytest.approx(expected_cov)
    assert result["max_min_ratio"] == pytest.approx(expected_ratio)
    assert result["max"] == pytest.approx(150.0)
    assert result["min"] == pytest.approx(50.0)


def test_calc_uniformity_result_contains_required_keys():
    """戻り値 dict が必須キーを全て含むこと."""
    luminance = numpy.array([[10.0, 20.0]], dtype=numpy.float64)
    result = calc_uniformity(luminance)
    assert set(result.keys()) == {"cov", "std", "max_min_ratio", "mean", "max", "min"}


def test_calc_uniformity_raises_on_zero_min():
    """最小値が 0 の輝度マップで ValueError が発生すること．

    仕様: Y_min = 0 の場合は計算不能 (SPEC_02 均一性指標節)
    """
    luminance = numpy.array([[0.0, 100.0, 200.0]], dtype=numpy.float64)
    with pytest.raises(ValueError):
        calc_uniformity(luminance)


def test_calc_uniformity_cov_formula():
    """CoV が σ / μ の公式に従うこと (SPEC_02)."""
    luminance = numpy.array([[80.0, 100.0, 120.0]], dtype=numpy.float64)
    result = calc_uniformity(luminance)
    expected_cov = result["std"] / result["mean"]
    assert result["cov"] == pytest.approx(expected_cov)
