"""
shared_utilities.pyのテスト

特にCommonPathServiceとget_common_path_service関数の
TARGET_PROJECT_ROOT環境変数対応機能をテスト


仕様書: SPEC-PRESENTATION
"""

import logging
import os
from pathlib import Path
from unittest.mock import Mock, patch

import pytest

from noveler.presentation.shared.shared_utilities import (
    CommonPathService,
    get_common_path_service,
    get_console,
    safe_console_print,
)


class TestCommonPathService:
    """CommonPathServiceのテストクラス"""

    @pytest.mark.spec("SPEC-PRESENTATION")
    def test_detect_project_root_with_target_project_root_env(self, tmp_path):
        """TARGET_PROJECT_ROOT環境変数による外部指定のテスト"""
        path_service = get_common_path_service()
        test_project_path = tmp_path / "test_project"
        test_project_path.mkdir()

        # 必要なディレクトリを作成
        (test_project_path / str(path_service.get_manuscript_dir())).mkdir()
        (test_project_path / str(path_service.get_management_dir())).mkdir()

        with patch.dict(os.environ, {"TARGET_PROJECT_ROOT": str(test_project_path)}):
            service = CommonPathService()
            assert service.project_root == test_project_path

    @pytest.mark.spec("SPEC-PRESENTATION")
    def test_detect_project_root_with_project_root_env_fallback(self, tmp_path):
        """PROJECT_ROOT環境変数による従来の外部指定のテスト"""
        test_project_path = tmp_path / "test_project"
        test_project_path.mkdir()

        # 必要なディレクトリを作成
        path_service = get_common_path_service()
        (test_project_path / str(path_service.get_manuscript_dir())).mkdir()
        (test_project_path / str(path_service.get_management_dir())).mkdir()

        with patch.dict(
            os.environ,
            {
                "PROJECT_ROOT": str(test_project_path),
                "TARGET_PROJECT_ROOT": "",  # TARGET_PROJECT_ROOTは空
            },
            clear=False,
        ):
            service = CommonPathService()
            assert service.project_root == test_project_path

    @pytest.mark.spec("SPEC-PRESENTATION")
    def test_detect_project_root_priority_order(self, tmp_path):
        """環境変数の優先順位テスト（TARGET_PROJECT_ROOT > PROJECT_ROOT）"""
        target_project_path = tmp_path / "target_project"
        regular_project_path = tmp_path / "regular_project"

        # 両方のプロジェクトディレクトリを作成
        for project_path in [target_project_path, regular_project_path]:
            project_path.mkdir()
            path_service = get_common_path_service()
            (project_path / str(path_service.get_manuscript_dir())).mkdir()
            (project_path / str(path_service.get_management_dir())).mkdir()

        with patch.dict(
            os.environ, {"TARGET_PROJECT_ROOT": str(target_project_path), "PROJECT_ROOT": str(regular_project_path)}
        ):
            service = CommonPathService()
            # TARGET_PROJECT_ROOTが優先される
            assert service.project_root == target_project_path

    @pytest.mark.spec("SPEC-PRESENTATION")
    def test_backward_compatibility_no_env_vars(self):
        """環境変数なしでの従来動作の確認"""
        with patch.dict(os.environ, {}, clear=True):
            # 環境変数なしで動作することを確認
            service = CommonPathService()
            # 現在のディレクトリがデフォルトになる
            assert service.project_root == Path.cwd()


class TestGetCommonPathService:
    """get_common_path_service関数のテストクラス"""

    def setup_method(self):
        """各テストメソッド実行前の初期化"""
        # グローバルインスタンスをリセット
        from noveler.presentation.cli import shared_utilities

        shared_utilities._common_path_service = None

    @pytest.mark.spec("SPEC-PRESENTATION")
    def test_get_common_path_service_with_target_project_root(self, tmp_path):
        """target_project_rootパラメータによる外部指定のテスト"""
        test_project_path = tmp_path / "target_project"
        test_project_path.mkdir()

        # 必要なディレクトリを作成
        path_service = get_common_path_service()
        (test_project_path / str(path_service.get_manuscript_dir())).mkdir()
        (test_project_path / str(path_service.get_management_dir())).mkdir()

        service = get_common_path_service(target_project_root=test_project_path)
        assert service.project_root == test_project_path

    @pytest.mark.spec("SPEC-PRESENTATION")
    def test_get_common_path_service_with_both_parameters(self, tmp_path):
        """project_rootとtarget_project_root両方指定時の動作テスト"""
        project_path = tmp_path / "project"
        target_path = tmp_path / "target"

        for path in [project_path, target_path]:
            path.mkdir()
            path_service = get_common_path_service()
            (path / str(path_service.get_manuscript_dir())).mkdir()
            (path / str(path_service.get_management_dir())).mkdir()

        # project_rootが優先される
        service = get_common_path_service(project_root=project_path, target_project_root=target_path)

        assert service.project_root == project_path

    @pytest.mark.spec("SPEC-PRESENTATION")
    def test_get_common_path_service_env_cleanup(self, tmp_path):
        """環境変数の適切なクリーンアップテスト"""
        test_project_path = tmp_path / "test_project"
        test_project_path.mkdir()

        # 元の環境変数値を保存
        original_value = os.environ.get("TARGET_PROJECT_ROOT")

        try:
            get_common_path_service(target_project_root=test_project_path)

            # 関数実行後、環境変数が元に戻されていることを確認
            assert os.environ.get("TARGET_PROJECT_ROOT") == original_value

        finally:
            # テスト環境のクリーンアップ
            if original_value is not None:
                os.environ["TARGET_PROJECT_ROOT"] = original_value
            else:
                os.environ.pop("TARGET_PROJECT_ROOT", None)

    @pytest.mark.spec("SPEC-PRESENTATION")
    def test_backward_compatibility_existing_usage(self, tmp_path):
        """既存コードとの後方互換性テスト"""
        test_project_path = tmp_path / "existing_project"
        test_project_path.mkdir()

        # 従来の使い方でも動作することを確認
        service = get_common_path_service(project_root=test_project_path)
        assert service.project_root == test_project_path

        # パラメータなしでも動作することを確認
        service_no_params = get_common_path_service()
        assert service_no_params is not None

    @pytest.mark.parametrize("env_var_name", ["TARGET_PROJECT_ROOT", "PROJECT_ROOT"])
    @pytest.mark.spec("SPEC-PRESENTATION")
    def test_environment_variable_integration(self, env_var_name, tmp_path):
        """環境変数との統合テスト"""
        test_project_path = tmp_path / "env_test_project"
        test_project_path.mkdir()
        path_service = get_common_path_service()
        (test_project_path / str(path_service.get_manuscript_dir())).mkdir()
        (test_project_path / str(path_service.get_management_dir())).mkdir()

        with patch.dict(os.environ, {env_var_name: str(test_project_path)}):
            service = get_common_path_service()
            assert service.project_root == test_project_path

    @pytest.mark.spec("SPEC-PRESENTATION")
    def test_get_common_path_service_resets_mock_cache(self):
        """モック化されたグローバルキャッシュを再初期化することを確認"""
        from noveler.presentation.shared import shared_utilities as shared_utils

        original_service = getattr(shared_utils, "_common_path_service", None)
        try:
            shared_utils._common_path_service = Mock()

            service = get_common_path_service()

            assert isinstance(service, CommonPathService)
        finally:
            shared_utils._common_path_service = original_service


class TestSafeConsolePrint:
    """safe_console_print() のエラーハンドリングテスト"""

    @pytest.mark.spec("SPEC-PRESENTATION")
    def test_safe_console_print_normal_output(self):
        """正常出力時は True を返す"""
        # Act: Rich Console への出力が成功することを確認
        result = safe_console_print("test message [blue]info[/blue]")

        # Assert: 戻り値が True であることを確認（出力成功）
        assert result is True

    @pytest.mark.spec("SPEC-PRESENTATION")
    def test_safe_console_print_with_oserror(self, caplog):
        """OSError 発生時は logger.error() で記録し False を返す"""
        # Arrange: console.print() を一時的にモック
        with patch.object(get_console(), "print", side_effect=OSError("broken pipe")):
            # Act
            with caplog.at_level(logging.ERROR):
                result = safe_console_print("test message")

            # Assert
            assert result is False
            assert any(
                "console output failed" in record.message
                and "broken pipe" in record.message
                for record in caplog.records
            )

    @pytest.mark.spec("SPEC-PRESENTATION")
    def test_safe_console_print_with_broken_pipe(self, caplog):
        """BrokenPipeError 発生時も logger.error() で記録し False を返す"""
        # Arrange
        with patch.object(get_console(), "print", side_effect=BrokenPipeError("broken pipe")):
            # Act
            with caplog.at_level(logging.ERROR):
                result = safe_console_print("test message")

            # Assert
            assert result is False
            assert "BrokenPipeError" in caplog.text
            assert "console output failed" in caplog.text

    @pytest.mark.spec("SPEC-PRESENTATION")
    def test_safe_console_print_with_ioerror(self, caplog):
        """IOError 発生時も logger.error() で記録し False を返す

        Note: Python 3.x では IOError は OSError のエイリアス
        """
        # Arrange
        with patch.object(get_console(), "print", side_effect=OSError("io error")):
            # Act
            with caplog.at_level(logging.ERROR):
                result = safe_console_print("test message")

            # Assert
            assert result is False
            # Python 3 では IOError は OSError のエイリアスなので OSError と表示される
            assert "console output failed" in caplog.text
            assert "io error" in caplog.text

    @pytest.mark.parametrize(
        ("exc_type", "exc_msg"),
        [
            (OSError, "os error"),
            (OSError, "io error"),
            (BrokenPipeError, "broken pipe"),
        ],
    )
    @pytest.mark.spec("SPEC-PRESENTATION")
    def test_safe_console_print_various_io_errors(self, exc_type, exc_msg, caplog):
        """様々な I/O 例外をハンドル"""
        # Arrange
        with patch.object(get_console(), "print", side_effect=exc_type(exc_msg)):
            # Act
            with caplog.at_level(logging.ERROR):
                result = safe_console_print("test")

            # Assert
            assert result is False
            assert exc_type.__name__ in caplog.text
            assert exc_msg in caplog.text

    @pytest.mark.spec("SPEC-PRESENTATION")
    def test_safe_console_print_with_rich_markup(self):
        """Rich マークアップ対応の確認"""
        # Act: Rich マークアップを含むメッセージが正常に出力されることを確認
        result = safe_console_print("[green]成功[/green] [yellow]警告[/yellow]")

        # Assert: 出力が成功したことを確認
        assert result is True


if __name__ == "__main__":
    pytest.main([__file__])
