import os
import pytest
import sys
from pathlib import Path
from sqlalchemy.exc import SQLAlchemyError

# Add the src directory to the path before importing project modules
sys.path.append(str(Path(__file__).parent.parent))

from src.local_deep_research.web.services.settings_manager import check_env_setting, SettingsManager, Setting, SettingType

def test_check_env_setting_exists(monkeypatch):
    monkeypatch.setenv("LDR_APP_VERSION", "1.0.0")
    assert check_env_setting("app.version") == "1.0.0"

def test_check_env_setting_not_exists():
    # Ensure the environment variable is not set
    if "LDR_APP_VERSION" in os.environ:
        del os.environ["LDR_APP_VERSION"]
    assert check_env_setting("app.version") is None

def test_get_setting_from_db(mocker):
    mock_db_session = mocker.MagicMock()
    mock_setting = Setting(key="app.version", value="1.0.0")
    # Use mocker.patch.object for patching instances
    mock_db_session.query.return_value.filter.return_value.all.return_value = [mock_setting]

    settings_manager = SettingsManager(db_session=mock_db_session)
    assert settings_manager.get_setting("app.version") == "1.0.0"

def test_get_setting_from_env(mocker, monkeypatch):
    mock_db_session = mocker.MagicMock()
    monkeypatch.setenv("LDR_APP_VERSION", "2.0.0")
    # Ensure DB query returns nothing to verify environment variable is prioritized
    mock_db_session.query.return_value.filter.return_value.all.return_value = []

    settings_manager = SettingsManager(db_session=mock_db_session)
    assert settings_manager.get_setting("app.version") == "2.0.0"

def test_get_setting_default(mocker):
    mock_db_session = mocker.MagicMock()
    # Ensure DB query returns nothing
    mock_db_session.query.return_value.filter.return_value.all.return_value = []

    settings_manager = SettingsManager(db_session=mock_db_session)
    assert settings_manager.get_setting("non_existent_setting", default="default_value") == "default_value"

# The mocker fixture is automatically available
def test_set_setting_update_existing(mocker):
    mock_db_session = mocker.MagicMock()
    mock_setting = Setting(key="app.version", value="1.0.0")
    mock_db_session.query.return_value.filter.return_value.first.return_value = mock_setting
    mocker.patch("src.local_deep_research.web.services.settings_manager.func.now") # Patching the func.now call

    settings_manager = SettingsManager(db_session=mock_db_session)
    result = settings_manager.set_setting("app.version", "2.0.0")

    assert result is True
    assert mock_setting.value == "2.0.0"
    mock_db_session.commit.assert_called_once()
    mock_db_session.rollback.assert_not_called()

def test_set_setting_create_new(mocker):
    mock_db_session = mocker.MagicMock()
    mock_db_session.query.return_value.filter.return_value.first.return_value = None
    mocker.patch("src.local_deep_research.web.services.settings_manager.func.now") # Patching the func.now call

    settings_manager = SettingsManager(db_session=mock_db_session)
    result = settings_manager.set_setting("new.setting", "new_value")

    assert result is True
    mock_db_session.add.assert_called_once()
    new_setting = mock_db_session.add.call_args[0][0]
    assert isinstance(new_setting, Setting)
    assert new_setting.key == "new.setting"
    assert new_setting.value == "new_value"
    assert new_setting.type == SettingType.APP # Assuming 'app' type for this key
    mock_db_session.commit.assert_called_once()
    mock_db_session.rollback.assert_not_called()

def test_set_setting_db_error(mocker):
    mock_db_session = mocker.MagicMock()
    # Set the side_effect to an instance of SQLAlchemyError
    mock_db_session.query.return_value.filter.return_value.first.side_effect = SQLAlchemyError("Simulated DB Error")

    # Mock the logger to check if error is logged
    mock_logger = mocker.patch("src.local_deep_research.web.services.settings_manager.logger")

    settings_manager = SettingsManager(db_session=mock_db_session)
    result = settings_manager.set_setting("app.version", "2.0.0")

    # Assert that the method returned False
    assert result is False

    mock_db_session.rollback.assert_called_once()
    mock_db_session.commit.assert_not_called()
    mock_logger.error.assert_called_once()
    # mock_logger.error.assert_called_once_with("Error setting value for app.version: Simulated DB Error")
