"""cli/visualize.py の仕様ベーステスト．

仕様出典:
- Phase 1 実装サマリー（sm-visualize CLI 仕様）
- docs/04_SPEC/SPEC_01_アーキテクチャ設計.md（CLI 層の責務）
- docs/01_GUIDE/GUIDE_08_テスト方針.md（テスト方針）

テスト方針:
- main() を直接呼び出す（subprocess 不使用：速度・カバレッジのため）
- ファイル I/O は tmp_path で実ファイルを使う（GUIDE_08）
- PyVista は off_screen=True 相当（screenshot 指定 or --off-screen）で実行

CLI 仕様（sm-visualize）:
  --scene / -s <yaml>   必須
  --no-bake             ベイクをスキップ
  --screenshot <path>   PNG を保存（off_screen 強制）
  --output / -o <dir>   ベイク結果を npz として保存
  --off-screen          ウィンドウを開かない
"""

from __future__ import annotations

from pathlib import Path

import numpy as np
import pytest

from sm_tias_lightsim.cli.visualize import main

# ---------------------------------------------------------------------------
# テスト用定数
# ---------------------------------------------------------------------------

MINIMAL_YAML = Path(__file__).parent.parent / "fixtures" / "minimal.yaml"


# ---------------------------------------------------------------------------
# 1. 必須引数の欠落（軽量 — argparse 動作）
# ---------------------------------------------------------------------------


def test_missing_scene_arg_exits_with_error():
    """--scene を渡さないと SystemExit が発生すること．

    仕様: --scene は必須引数（required=True）
    """
    with pytest.raises(SystemExit) as exc_info:
        main([])
    assert exc_info.value.code != 0


def test_missing_scene_arg_with_other_args_exits_with_error():
    """--scene なしで他の引数を渡すと SystemExit が発生すること．"""
    with pytest.raises(SystemExit) as exc_info:
        main(["--no-bake"])
    assert exc_info.value.code != 0


# ---------------------------------------------------------------------------
# 2. 存在しない YAML パス（軽量 — return code 1）
# ---------------------------------------------------------------------------


def test_nonexistent_scene_returns_1(tmp_path: Path, capsys):
    """存在しない YAML パスを渡すと return 1 かつ stderr にエラーが出ること．

    仕様: FileNotFoundError → stderr「シーン YAML ファイルが見つかりません」→ return 1
    """
    nonexistent = tmp_path / "does_not_exist.yaml"
    result = main(["--scene", str(nonexistent), "--no-bake", "--off-screen"])
    assert result == 1
    captured = capsys.readouterr()
    assert "シーン YAML ファイルが見つかりません" in captured.err


def test_nonexistent_scene_error_message_is_on_stderr(tmp_path: Path, capsys):
    """エラーメッセージが stderr に出ること．"""
    nonexistent = tmp_path / "missing.yaml"
    main(["--scene", str(nonexistent), "--no-bake", "--off-screen"])
    captured = capsys.readouterr()
    assert "エラー:" in captured.err


# ---------------------------------------------------------------------------
# 3. 不正 YAML（軽量 — return code 1）
# ---------------------------------------------------------------------------


def test_invalid_yaml_returns_1(invalid_yaml_file: Path, capsys):
    """必須フィールド欠落の YAML を渡すと return 1 が返ること．

    仕様: Pydantic ValidationError → stderr「シーン YAML の読み込みに失敗しました」→ return 1
    """
    result = main(["--scene", str(invalid_yaml_file), "--no-bake", "--off-screen"])
    assert result == 1


def test_invalid_yaml_stderr_message(invalid_yaml_file: Path, capsys):
    """不正 YAML のエラーメッセージに「シーン YAML の読み込みに失敗しました」が含まれること．"""
    main(["--scene", str(invalid_yaml_file), "--no-bake", "--off-screen"])
    captured = capsys.readouterr()
    assert "シーン YAML の読み込みに失敗しました" in captured.err


# ---------------------------------------------------------------------------
# 4. --no-bake モード（slow — PyVista 実行）
# ---------------------------------------------------------------------------


@pytest.mark.slow
def test_no_bake_with_screenshot_creates_png(tmp_path: Path):
    """--no-bake + --screenshot で PNG が生成されること（ベイクなし）．

    仕様: --no-bake のとき bake_irradiance をスキップする
    """
    screenshot_path = tmp_path / "no_bake_out.png"
    result = main([
        "--scene", str(MINIMAL_YAML),
        "--no-bake",
        "--screenshot", str(screenshot_path),
    ])
    assert result == 0, "正常終了 (return 0)"
    assert screenshot_path.exists(), "--no-bake + --screenshot で PNG が生成されること"
    assert screenshot_path.stat().st_size > 0, "PNG ファイルが空でないこと"


@pytest.mark.slow
def test_no_bake_does_not_print_bake_start(tmp_path: Path, capsys):
    """--no-bake のとき「照度ベイクを開始します」が stdout に出ないこと．

    仕様: --no-bake が指定されたとき「--no-bake: 照度ベイクをスキップします」を出力する
    """
    screenshot_path = tmp_path / "out.png"
    main([
        "--scene", str(MINIMAL_YAML),
        "--no-bake",
        "--screenshot", str(screenshot_path),
    ])
    captured = capsys.readouterr()
    assert "照度ベイクを開始します" not in captured.out
    assert "スキップ" in captured.out


# ---------------------------------------------------------------------------
# 5. --screenshot で PNG が生成されること（slow — ベイク + PyVista 実行）
# ---------------------------------------------------------------------------


@pytest.mark.slow
def test_screenshot_creates_png(tmp_path: Path):
    """--screenshot で PNG が生成されること（ベイクあり）．

    仕様: --screenshot <path> PNG を保存（off_screen 強制）
    """
    screenshot_path = tmp_path / "out.png"
    result = main([
        "--scene", str(MINIMAL_YAML),
        "--screenshot", str(screenshot_path),
    ])
    assert result == 0, "正常終了 (return 0)"
    assert screenshot_path.exists(), "--screenshot で PNG が生成されること"
    assert screenshot_path.stat().st_size > 0


@pytest.mark.slow
def test_screenshot_stdout_contains_save_message(tmp_path: Path, capsys):
    """--screenshot 実行後に stdout に「スクリーンショット保存」が含まれること．

    仕様: screenshot 指定時は完了後に「スクリーンショット保存: <path>」を出力する
    """
    screenshot_path = tmp_path / "out.png"
    main([
        "--scene", str(MINIMAL_YAML),
        "--screenshot", str(screenshot_path),
    ])
    captured = capsys.readouterr()
    assert "スクリーンショット保存" in captured.out


# ---------------------------------------------------------------------------
# 6. --output でベイク結果 npz が保存されること（slow）
# ---------------------------------------------------------------------------


@pytest.mark.slow
def test_output_dir_creates_npz(tmp_path: Path):
    """--output 指定でベイク結果 npz が保存されること．

    仕様: --output / -o <dir> ベイク結果を {config.name}_irradiance.npz として保存
    """
    output_dir = tmp_path / "bake_out"
    screenshot_path = tmp_path / "out.png"
    result = main([
        "--scene", str(MINIMAL_YAML),
        "--output", str(output_dir),
        "--screenshot", str(screenshot_path),
    ])
    assert result == 0

    # {config.name}_irradiance.npz が存在すること
    # minimal.yaml の name: smoke_test
    npz_path = output_dir / "smoke_test_irradiance.npz"
    assert npz_path.exists(), f"npz ファイルが生成されること: {npz_path}"

    # npz が正しくロードできること
    data = np.load(str(npz_path))
    assert len(data.files) > 0, "npz に少なくとも 1 つのキーが含まれること"


@pytest.mark.slow
def test_output_dir_is_created_automatically(tmp_path: Path):
    """--output で指定したディレクトリが自動生成されること．

    仕様: output_dir.mkdir(parents=True, exist_ok=True) で親ディレクトリも含めて作成する
    """
    output_dir = tmp_path / "deep" / "nested" / "bake"
    screenshot_path = tmp_path / "out.png"

    assert not output_dir.exists(), "テスト前にはディレクトリが存在しないこと"

    result = main([
        "--scene", str(MINIMAL_YAML),
        "--output", str(output_dir),
        "--screenshot", str(screenshot_path),
    ])
    assert result == 0
    assert output_dir.exists(), "深いネストのディレクトリが自動作成されること"


# ---------------------------------------------------------------------------
# 7. 短縮引数 -s / -o（slow）
# ---------------------------------------------------------------------------


@pytest.mark.slow
def test_short_args_s_and_o_work(tmp_path: Path):
    """-s と -o の短縮形でも --scene / --output と同等に動くこと．

    仕様: --scene/-s, --output/-o の短縮形を提供する
    """
    output_dir = tmp_path / "short_out"
    screenshot_path = tmp_path / "out.png"

    result = main([
        "-s", str(MINIMAL_YAML),
        "-o", str(output_dir),
        "--screenshot", str(screenshot_path),
    ])
    assert result == 0
    assert (output_dir / "smoke_test_irradiance.npz").exists()


# ---------------------------------------------------------------------------
# 8. --off-screen + ベイクのみ（screenshot なし，no-op）（slow）
# ---------------------------------------------------------------------------


@pytest.mark.slow
def test_off_screen_without_screenshot_returns_0(tmp_path: Path):
    """--off-screen + screenshot なしで no-op になり return 0 が返ること．

    仕様: screenshot=None かつ off_screen=True のときは no-op
    """
    result = main([
        "--scene", str(MINIMAL_YAML),
        "--no-bake",
        "--off-screen",
    ])
    assert result == 0, "no-op でも正常終了すること"
