"""
Tests for server_config module.

Covers server configuration management for web app startup including
loading, saving, and syncing configuration settings.
"""

import json
from pathlib import Path
from unittest.mock import patch


from tests.test_utils import add_src_to_path

add_src_to_path()


class TestGetServerConfigPath:
    """Tests for get_server_config_path function."""

    def test_returns_path_object(self, tmp_path):
        """Should return a Path object."""
        with patch(
            "local_deep_research.web.server_config.get_data_dir",
            return_value=str(tmp_path),
        ):
            from local_deep_research.web.server_config import (
                get_server_config_path,
            )

            result = get_server_config_path()

            assert isinstance(result, Path)

    def test_correct_filename(self, tmp_path):
        """Should use correct filename."""
        with patch(
            "local_deep_research.web.server_config.get_data_dir",
            return_value=str(tmp_path),
        ):
            from local_deep_research.web.server_config import (
                get_server_config_path,
            )

            result = get_server_config_path()

            assert result.name == "server_config.json"

    def test_path_under_data_dir(self, tmp_path):
        """Should be under the data directory."""
        with patch(
            "local_deep_research.web.server_config.get_data_dir",
            return_value=str(tmp_path),
        ):
            from local_deep_research.web.server_config import (
                get_server_config_path,
            )

            result = get_server_config_path()

            assert result.parent == tmp_path


class TestLoadServerConfig:
    """Tests for load_server_config function."""

    def test_returns_defaults_when_no_file(self, tmp_path):
        """Should return default config when file doesn't exist."""
        with patch(
            "local_deep_research.web.server_config.get_data_dir",
            return_value=str(tmp_path),
        ):
            with patch(
                "local_deep_research.web.server_config.get_typed_setting_value",
                side_effect=lambda key, val, typ, default: default,
            ):
                from local_deep_research.web.server_config import (
                    load_server_config,
                )

                result = load_server_config()

                assert result["host"] == "0.0.0.0"
                assert result["port"] == 5000
                assert result["debug"] is False
                assert result["use_https"] is True

    def test_loads_from_existing_file(self, tmp_path):
        """Should load config from existing file."""
        config_file = tmp_path / "server_config.json"
        config_data = {
            "host": "127.0.0.1",
            "port": 8080,
            "debug": True,
            "use_https": False,
        }
        config_file.write_text(json.dumps(config_data))

        with patch(
            "local_deep_research.web.server_config.get_data_dir",
            return_value=str(tmp_path),
        ):
            with patch(
                "local_deep_research.web.server_config.get_typed_setting_value",
                side_effect=lambda key, val, typ, default: val
                if val is not None
                else default,
            ):
                from local_deep_research.web.server_config import (
                    load_server_config,
                )

                result = load_server_config()

                assert result["host"] == "127.0.0.1"
                assert result["port"] == 8080
                assert result["debug"] is True
                assert result["use_https"] is False

    def test_handles_corrupt_json(self, tmp_path):
        """Should handle corrupt JSON gracefully."""
        config_file = tmp_path / "server_config.json"
        config_file.write_text("{ invalid json }")

        with patch(
            "local_deep_research.web.server_config.get_data_dir",
            return_value=str(tmp_path),
        ):
            with patch(
                "local_deep_research.web.server_config.get_typed_setting_value",
                side_effect=lambda key, val, typ, default: default,
            ):
                with patch(
                    "local_deep_research.web.server_config.logger"
                ) as mock_logger:
                    from local_deep_research.web.server_config import (
                        load_server_config,
                    )

                    result = load_server_config()

                    # Should log warning
                    mock_logger.warning.assert_called_once()
                    # Should return defaults
                    assert result["host"] == "0.0.0.0"
                    assert result["port"] == 5000

    def test_includes_rate_limit_settings(self, tmp_path):
        """Should include rate limit settings."""
        with patch(
            "local_deep_research.web.server_config.get_data_dir",
            return_value=str(tmp_path),
        ):
            with patch(
                "local_deep_research.web.server_config.get_typed_setting_value",
                side_effect=lambda key, val, typ, default: default,
            ):
                from local_deep_research.web.server_config import (
                    load_server_config,
                )

                result = load_server_config()

                assert "rate_limit_default" in result
                assert "rate_limit_login" in result
                assert "rate_limit_registration" in result

    def test_includes_allow_registrations(self, tmp_path):
        """Should include allow_registrations setting."""
        with patch(
            "local_deep_research.web.server_config.get_data_dir",
            return_value=str(tmp_path),
        ):
            with patch(
                "local_deep_research.web.server_config.get_typed_setting_value",
                side_effect=lambda key, val, typ, default: default,
            ):
                from local_deep_research.web.server_config import (
                    load_server_config,
                )

                result = load_server_config()

                assert "allow_registrations" in result
                assert result["allow_registrations"] is True

    def test_unrecognized_allow_registrations_defaults_to_false(
        self, tmp_path, monkeypatch
    ):
        """Unrecognized LDR_APP_ALLOW_REGISTRATIONS value should default to False (fail closed)."""
        monkeypatch.setenv("LDR_APP_ALLOW_REGISTRATIONS", "disabled")

        with patch(
            "local_deep_research.web.server_config.get_data_dir",
            return_value=str(tmp_path),
        ):
            with patch(
                "local_deep_research.web.server_config.get_typed_setting_value",
                side_effect=lambda key, val, typ, default: default,
            ):
                with patch(
                    "local_deep_research.web.server_config.logger"
                ) as mock_logger:
                    from local_deep_research.web.server_config import (
                        load_server_config,
                    )

                    result = load_server_config()

                    # Should have warned about unrecognized value
                    warning_calls = [
                        str(c) for c in mock_logger.warning.call_args_list
                    ]
                    assert any(
                        "disabled" in call and "recognized" in call
                        for call in warning_calls
                    ), (
                        f"Expected warning about 'disabled', got: {warning_calls}"
                    )

                    # Should default to False (fail closed) for security
                    assert result["allow_registrations"] is False

    def test_no_warning_on_recognized_allow_registrations_value(
        self, tmp_path, monkeypatch
    ):
        """Should NOT warn when LDR_APP_ALLOW_REGISTRATIONS is a recognized boolean value."""
        monkeypatch.setenv("LDR_APP_ALLOW_REGISTRATIONS", "false")

        with patch(
            "local_deep_research.web.server_config.get_data_dir",
            return_value=str(tmp_path),
        ):
            with patch(
                "local_deep_research.web.server_config.get_typed_setting_value",
                side_effect=lambda key, val, typ, default: default,
            ):
                with patch(
                    "local_deep_research.web.server_config.logger"
                ) as mock_logger:
                    from local_deep_research.web.server_config import (
                        load_server_config,
                    )

                    load_server_config()

                    # No warning should contain "recognized"
                    warning_calls = [
                        str(c) for c in mock_logger.warning.call_args_list
                    ]
                    assert not any(
                        "recognized" in call for call in warning_calls
                    ), (
                        f"Unexpected warning about recognized value: {warning_calls}"
                    )


class TestSaveServerConfig:
    """Tests for save_server_config function."""

    def test_saves_to_file(self, tmp_path):
        """Should save config to file."""
        with patch(
            "local_deep_research.web.server_config.get_data_dir",
            return_value=str(tmp_path),
        ):
            from local_deep_research.web.server_config import save_server_config

            config = {"host": "localhost", "port": 3000}
            save_server_config(config)

            config_file = tmp_path / "server_config.json"
            assert config_file.exists()
            saved_data = json.loads(config_file.read_text())
            assert saved_data["host"] == "localhost"
            assert saved_data["port"] == 3000

    def test_creates_parent_directory(self, tmp_path):
        """Should create parent directory if it doesn't exist."""
        nested_path = tmp_path / "nested" / "dir"

        with patch(
            "local_deep_research.web.server_config.get_data_dir",
            return_value=str(nested_path),
        ):
            from local_deep_research.web.server_config import save_server_config

            config = {"host": "localhost"}
            save_server_config(config)

            config_file = nested_path / "server_config.json"
            assert config_file.exists()

    def test_handles_write_errors(self, tmp_path):
        """Should handle write errors gracefully."""
        with patch(
            "local_deep_research.web.server_config.get_data_dir",
            return_value=str(tmp_path),
        ):
            with patch(
                "builtins.open", side_effect=PermissionError("No write access")
            ):
                with patch(
                    "local_deep_research.web.server_config.logger"
                ) as mock_logger:
                    from local_deep_research.web.server_config import (
                        save_server_config,
                    )

                    # Should not raise, but log exception
                    save_server_config({"host": "localhost"})

                    mock_logger.exception.assert_called_once()

    def test_logs_success(self, tmp_path):
        """Should log success when saved."""
        with patch(
            "local_deep_research.web.server_config.get_data_dir",
            return_value=str(tmp_path),
        ):
            with patch(
                "local_deep_research.web.server_config.logger"
            ) as mock_logger:
                from local_deep_research.web.server_config import (
                    save_server_config,
                )

                save_server_config({"host": "localhost"})

                mock_logger.info.assert_called_once()
                call_args = mock_logger.info.call_args[0][0]
                assert "saved" in call_args.lower()

    def test_formats_json_with_indent(self, tmp_path):
        """Should format JSON with indentation."""
        with patch(
            "local_deep_research.web.server_config.get_data_dir",
            return_value=str(tmp_path),
        ):
            from local_deep_research.web.server_config import save_server_config

            config = {"host": "localhost", "port": 3000}
            save_server_config(config)

            config_file = tmp_path / "server_config.json"
            content = config_file.read_text()
            # Indented JSON has newlines
            assert "\n" in content


class TestSyncFromSettings:
    """Tests for sync_from_settings function."""

    def test_updates_host_from_settings(self, tmp_path):
        """Should update host from settings snapshot."""
        with patch(
            "local_deep_research.web.server_config.get_data_dir",
            return_value=str(tmp_path),
        ):
            with patch(
                "local_deep_research.web.server_config.get_typed_setting_value",
                side_effect=lambda key, val, typ, default: val
                if val is not None
                else default,
            ):
                from local_deep_research.web.server_config import (
                    sync_from_settings,
                )

                sync_from_settings({"web.host": "192.168.1.1"})

                config_file = tmp_path / "server_config.json"
                saved_data = json.loads(config_file.read_text())
                assert saved_data["host"] == "192.168.1.1"

    def test_updates_port_from_settings(self, tmp_path):
        """Should update port from settings snapshot."""
        with patch(
            "local_deep_research.web.server_config.get_data_dir",
            return_value=str(tmp_path),
        ):
            with patch(
                "local_deep_research.web.server_config.get_typed_setting_value",
                side_effect=lambda key, val, typ, default: val
                if val is not None
                else default,
            ):
                from local_deep_research.web.server_config import (
                    sync_from_settings,
                )

                sync_from_settings({"web.port": 9000})

                config_file = tmp_path / "server_config.json"
                saved_data = json.loads(config_file.read_text())
                assert saved_data["port"] == 9000

    def test_updates_all_supported_settings(self, tmp_path):
        """Should update all supported settings."""
        with patch(
            "local_deep_research.web.server_config.get_data_dir",
            return_value=str(tmp_path),
        ):
            with patch(
                "local_deep_research.web.server_config.get_typed_setting_value",
                side_effect=lambda key, val, typ, default: val
                if val is not None
                else default,
            ):
                from local_deep_research.web.server_config import (
                    sync_from_settings,
                )

                settings = {
                    "web.host": "10.0.0.1",
                    "web.port": 4000,
                    "app.debug": True,
                    "web.use_https": False,
                    "app.allow_registrations": False,
                    "security.rate_limit_default": "100 per hour",
                    "security.rate_limit_login": "10 per minute",
                    "security.rate_limit_registration": "1 per hour",
                }
                sync_from_settings(settings)

                config_file = tmp_path / "server_config.json"
                saved_data = json.loads(config_file.read_text())

                assert saved_data["host"] == "10.0.0.1"
                assert saved_data["port"] == 4000
                assert saved_data["debug"] is True
                assert saved_data["use_https"] is False
                assert saved_data["allow_registrations"] is False
                assert saved_data["rate_limit_default"] == "100 per hour"
                assert saved_data["rate_limit_login"] == "10 per minute"
                assert saved_data["rate_limit_registration"] == "1 per hour"

    def test_ignores_unknown_settings(self, tmp_path):
        """Should ignore settings not in the known list."""
        with patch(
            "local_deep_research.web.server_config.get_data_dir",
            return_value=str(tmp_path),
        ):
            with patch(
                "local_deep_research.web.server_config.get_typed_setting_value",
                side_effect=lambda key, val, typ, default: val
                if val is not None
                else default,
            ):
                from local_deep_research.web.server_config import (
                    sync_from_settings,
                )

                sync_from_settings(
                    {
                        "web.host": "localhost",
                        "unknown.setting": "should be ignored",
                    }
                )

                config_file = tmp_path / "server_config.json"
                saved_data = json.loads(config_file.read_text())

                assert saved_data["host"] == "localhost"
                assert "unknown.setting" not in saved_data
                assert "unknown" not in saved_data

    def test_preserves_existing_values(self, tmp_path):
        """Should preserve values not in the settings snapshot."""
        # Create initial config
        config_file = tmp_path / "server_config.json"
        initial_config = {
            "host": "original-host",
            "port": 1234,
            "debug": False,
            "use_https": True,
        }
        config_file.write_text(json.dumps(initial_config))

        with patch(
            "local_deep_research.web.server_config.get_data_dir",
            return_value=str(tmp_path),
        ):
            with patch(
                "local_deep_research.web.server_config.get_typed_setting_value",
                side_effect=lambda key, val, typ, default: val
                if val is not None
                else default,
            ):
                from local_deep_research.web.server_config import (
                    sync_from_settings,
                )

                # Only update host
                sync_from_settings({"web.host": "new-host"})

                saved_data = json.loads(config_file.read_text())

                assert saved_data["host"] == "new-host"
                assert saved_data["port"] == 1234  # Preserved
                assert saved_data["debug"] is False  # Preserved
                assert saved_data["use_https"] is True  # Preserved

    def test_empty_settings_snapshot(self, tmp_path):
        """Should handle empty settings snapshot."""
        with patch(
            "local_deep_research.web.server_config.get_data_dir",
            return_value=str(tmp_path),
        ):
            with patch(
                "local_deep_research.web.server_config.get_typed_setting_value",
                side_effect=lambda key, val, typ, default: default,
            ):
                from local_deep_research.web.server_config import (
                    sync_from_settings,
                )

                # Should not raise
                sync_from_settings({})

                config_file = tmp_path / "server_config.json"
                assert config_file.exists()
