# pyright: reportArgumentType=none, reportCallIssue=none
"""scene/schema.py の MetricsConfig.fullimage_min_max / fullimage_sigma の仕様ベーステスト．

仕様出典:
- docs/04_SPEC/SPEC_02_シーン定義仕様.md（evaluation/metrics セクション．オプション指標は
  bool フラグで有効化し，既定 False で既存 YAML はそのまま動く）
- docs/04_SPEC/SPEC_04_評価仕様.md「動径 min/max 比（オプション）」（既定 False・
  metrics.csv に列を追加するオプション指標の扱い）
- evaluation/fullimage.py（新指標の定義．sigma はガウス平滑化の σ で正の値のみ意味を持つ）

検証する仕様:
- fullimage_min_max の既定は False（後方互換: フィールドを書いていない既存 YAML が読める）
- fullimage_sigma の既定は 12.0（464x450 の ROI で検証した値）
- fullimage_sigma は正の値のみ受理する（0・負は ValidationError）
- 既存フィールド（cov / std / max_min / radial_profile / radial_min_max / shape_metrics）に
  干渉しない

テスト方針（.claude/rules/testing.md）: 入力バリデーションは必ずテストする．
Pydantic モデルの直接構築と YAML ロードの両方で確認する．
"""

from __future__ import annotations

from pathlib import Path

import pytest
import yaml
from pydantic import ValidationError

from sm_tias_lightsim.scene.schema import EvaluationConfig, MetricsConfig

_REPO_ROOT = Path(__file__).resolve().parents[2]

_MINIMAL_ROI_YAML = """\
roi:
  type: image_pixel
  x: 0
  y: 0
  width: 100
  height: 100
metrics:
  cov: true
  std: true
  max_min: true
  radial_profile: true
"""


def _eval_config_from_metrics_yaml(metrics_lines: str) -> EvaluationConfig:
    """metrics セクションに追記した YAML を EvaluationConfig にする．"""
    data = yaml.safe_load(_MINIMAL_ROI_YAML + metrics_lines)
    return EvaluationConfig.model_validate(data)


# ---------------------------------------------------------------------------
# A. 既定値（後方互換）
# ---------------------------------------------------------------------------


def test_fullimage_min_max_default_is_false():
    """MetricsConfig() の fullimage_min_max 既定値が False であること．

    仕様: オプション指標なので既定 False（既存挙動互換）．
    """
    assert MetricsConfig().fullimage_min_max is False


def test_fullimage_sigma_default_is_twelve():
    """MetricsConfig() の fullimage_sigma 既定値が 12.0 であること．

    仕様: 既定 12.0 は 464x450 の ROI で検証した値（fullimage.py docstring）．
    """
    assert MetricsConfig().fullimage_sigma == pytest.approx(12.0)


def test_existing_yaml_without_fullimage_fields_is_loadable():
    """フィールドを持たない既存 YAML がそのまま読め，既定値になること（後方互換）．"""
    config = _eval_config_from_metrics_yaml("")

    assert config.metrics is not None
    assert config.metrics.fullimage_min_max is False
    assert config.metrics.fullimage_sigma == pytest.approx(12.0)


@pytest.mark.parametrize(
    "config_name",
    ["eval_baseline.yaml", "eval_baseline_linear.yaml", "eval_real.yaml"],
)
def test_shipped_existing_configs_keep_fullimage_disabled(config_name: str):
    """同梱の既存評価 YAML が無修正で読め，新指標が無効のままであること（後方互換）．"""
    data = yaml.safe_load((_REPO_ROOT / "configs" / config_name).read_text(encoding="utf-8"))
    config = EvaluationConfig.model_validate(data)

    assert config.metrics is not None
    assert config.metrics.fullimage_min_max is False


def test_shipped_dng_target_shape_config_enables_fullimage():
    """configs/eval_baseline_dng_target_shape.yaml で新指標が有効になっていること．

    仕様: 実機比較用の評価 config で metrics.fullimage_min_max: true を有効化する．
    """
    path = _REPO_ROOT / "configs" / "eval_baseline_dng_target_shape.yaml"
    config = EvaluationConfig.model_validate(yaml.safe_load(path.read_text(encoding="utf-8")))

    assert config.metrics is not None
    assert config.metrics.fullimage_min_max is True
    assert config.metrics.fullimage_sigma == pytest.approx(12.0)


# ---------------------------------------------------------------------------
# B. 明示指定と YAML ロード
# ---------------------------------------------------------------------------


@pytest.mark.parametrize("enabled", [True, False])
def test_fullimage_min_max_explicit_value_is_kept(enabled: bool):
    """MetricsConfig(fullimage_min_max=...) の明示指定がそのまま保持されること．"""
    assert MetricsConfig(fullimage_min_max=enabled).fullimage_min_max is enabled


def test_yaml_load_fullimage_min_max_true():
    """YAML の metrics.fullimage_min_max: true が反映されること．"""
    config = _eval_config_from_metrics_yaml("  fullimage_min_max: true\n")

    assert config.metrics is not None
    assert config.metrics.fullimage_min_max is True


def test_yaml_load_fullimage_min_max_false():
    """YAML の metrics.fullimage_min_max: false が反映されること．"""
    config = _eval_config_from_metrics_yaml("  fullimage_min_max: false\n")

    assert config.metrics is not None
    assert config.metrics.fullimage_min_max is False


@pytest.mark.parametrize("sigma", [0.5, 8.0, 12.0, 30.0])
def test_yaml_load_fullimage_sigma_positive_values(sigma: float):
    """正の fullimage_sigma が YAML から読めること．"""
    config = _eval_config_from_metrics_yaml(
        f"  fullimage_min_max: true\n  fullimage_sigma: {sigma}\n"
    )

    assert config.metrics is not None
    assert config.metrics.fullimage_sigma == pytest.approx(sigma)


def test_fullimage_sigma_accepts_integer_yaml_value():
    """YAML の整数値 sigma が float として読めること．"""
    config = _eval_config_from_metrics_yaml("  fullimage_sigma: 8\n")

    assert config.metrics is not None
    assert config.metrics.fullimage_sigma == pytest.approx(8.0)


# ---------------------------------------------------------------------------
# C. バリデーション（不正値は ValidationError）
# ---------------------------------------------------------------------------


@pytest.mark.parametrize("sigma", [0.0, -1.0, -12.0])
def test_non_positive_fullimage_sigma_raises_validation_error(sigma: float):
    """fullimage_sigma が 0 以下なら ValidationError になること．

    仕様: sigma はガウス平滑化の σ なので正の値のみ（fullimage.py も 0 以下を拒否する）．
    """
    with pytest.raises(ValidationError):
        MetricsConfig(fullimage_sigma=sigma)


@pytest.mark.parametrize("sigma", ["0", "-3.0"])
def test_non_positive_fullimage_sigma_in_yaml_raises_validation_error(sigma: str):
    """YAML 経由でも 0 以下の fullimage_sigma が ValidationError になること．"""
    with pytest.raises(ValidationError):
        _eval_config_from_metrics_yaml(f"  fullimage_sigma: {sigma}\n")


def test_non_numeric_fullimage_sigma_raises_validation_error():
    """数値でない fullimage_sigma は ValidationError になること．"""
    with pytest.raises(ValidationError):
        MetricsConfig(fullimage_sigma="wide")


def test_non_boolean_fullimage_min_max_raises_validation_error():
    """bool に解釈できない fullimage_min_max は ValidationError になること．"""
    with pytest.raises(ValidationError):
        MetricsConfig(fullimage_min_max="maybe")


# ---------------------------------------------------------------------------
# D. 既存フィールドへの非干渉
# ---------------------------------------------------------------------------


def test_enabling_fullimage_does_not_change_other_metric_flags():
    """新フィールドを有効にしても既存フラグの既定値が変わらないこと．"""
    default = MetricsConfig()
    enabled = MetricsConfig(fullimage_min_max=True, fullimage_sigma=20.0)

    assert enabled.cov == default.cov
    assert enabled.std == default.std
    assert enabled.max_min == default.max_min
    assert enabled.radial_profile == default.radial_profile
    assert enabled.radial_min_max == default.radial_min_max
    assert enabled.shape_metrics == default.shape_metrics


def test_metrics_config_is_frozen():
    """MetricsConfig が immutable であること（読み込み後に書き換えない）．"""
    config = MetricsConfig(fullimage_min_max=True)

    with pytest.raises(ValidationError):
        config.fullimage_min_max = False
