"""Tests for the ACB SQLite adapter."""
import tempfile
from pathlib import Path
from unittest.mock import Mock, AsyncMock, patch, MagicMock

import pytest
from pydantic import SecretStr
from sqlalchemy.engine import URL

from acb.adapters.sql.sqlite import SqlSettings, Sql
from acb.adapters.sql._base import SqlBaseSettings
from acb.config import Config


class TestSqlSettings:
    """Test the SqlSettings class."""

    def test_sql_settings_defaults(self) -> None:
        """Test SqlSettings default values."""
        settings = SqlSettings()

        assert settings.database_url == "sqlite:///data/app.db"
        assert settings._driver == "sqlite+pysqlite"
        assert settings._async_driver == "sqlite+aiosqlite"
        assert settings.auth_token is None
        assert settings.wal_mode is True
        assert settings.port is None
        assert settings.host is None
        assert settings.user is None
        assert settings.password is None

    def test_sql_settings_custom_values(self) -> None:
        """Test SqlSettings with custom values."""
        settings = SqlSettings(
            database_url="sqlite:///test.db",
            auth_token=SecretStr("test-token"),
            wal_mode=False,
            port=1234,
            host=SecretStr("localhost"),
            user=SecretStr("testuser"),
            password=SecretStr("testpass")
        )

        assert settings.database_url == "sqlite:///test.db"
        assert settings.auth_token.get_secret_value() == "test-token"
        assert settings.wal_mode is False
        assert settings.port == 1234
        assert settings.host.get_secret_value() == "localhost"
        assert settings.user.get_secret_value() == "testuser"
        assert settings.password.get_secret_value() == "testpass"

    def test_validate_database_url_valid(self) -> None:
        """Test database URL validation with valid URLs."""
        valid_urls = [
            "sqlite:///test.db",
            "sqlite+pysqlite:///test.db",
            "sqlite+aiosqlite:///test.db",
            "libsql://test.db",
            "https://test.turso.io",
        ]

        for url in valid_urls:
            # Should not raise an exception
            settings = SqlSettings(database_url=url)
            assert settings.database_url == url

    def test_validate_database_url_invalid(self) -> None:
        """Test database URL validation with invalid URLs."""
        invalid_urls = [
            "postgresql://localhost/test",
            "mysql://localhost/test",
            "invalid://test.db",
        ]

        for url in invalid_urls:
            with pytest.raises(ValueError):
                SqlSettings(database_url=url)

    def test_is_turso_url_detection(self) -> None:
        """Test Turso URL detection."""
        # Test Turso URLs
        turso_urls = [
            "libsql://test.turso.io",
            "https://test.turso.io",
            "sqlite://test.turso.io?authToken=token",
            "libsql://test.db",
        ]

        for url in turso_urls:
            settings = SqlSettings(database_url=url)
            assert settings._is_turso_url() is True
            assert settings.is_turso is True

    def test_is_turso_url_non_turso(self) -> None:
        """Test Turso URL detection for non-Turso URLs."""
        non_turso_urls = [
            "sqlite:///test.db",
            "sqlite+pysqlite:///test.db",
            "sqlite+aiosqlite:///test.db",
        ]

        for url in non_turso_urls:
            settings = SqlSettings(database_url=url)
            assert settings._is_turso_url() is False
            assert settings.is_turso is False

    def test_setup_drivers_turso(self) -> None:
        """Test driver setup for Turso URLs."""
        settings = SqlSettings(database_url="libsql://test.turso.io")
        assert settings.driver == "sqlite+libsql"
        assert settings.async_driver == "sqlite+libsql"

    def test_setup_drivers_local(self) -> None:
        """Test driver setup for local SQLite URLs."""
        settings = SqlSettings(database_url="sqlite:///test.db")
        assert settings.driver == "sqlite+pysqlite"
        assert settings.async_driver == "sqlite+aiosqlite"

    def test_setup_turso_urls_with_auth_token(self) -> None:
        """Test Turso URL setup with auth token."""
        settings = SqlSettings(
            database_url="libsql://test.turso.io",
            auth_token=SecretStr("test-token")
        )

        # Just ensure it doesn't raise an exception
        # The actual URL construction is complex and depends on many factors
        assert settings._async_url is not None

    def test_setup_turso_urls_with_query_params(self) -> None:
        """Test Turso URL setup with query parameters."""
        settings = SqlSettings(
            database_url="https://test.turso.io?authToken=test-token&secure=true"
        )

        # Just ensure it doesn't raise an exception
        assert settings._async_url is not None

    def test_setup_local_urls_with_wal_mode(self) -> None:
        """Test local URL setup with WAL mode enabled."""
        with tempfile.TemporaryDirectory() as tmpdir:
            db_path = Path(tmpdir) / "test.db"
            settings = SqlSettings(
                database_url=f"sqlite:///{db_path}",
                wal_mode=True
            )

            assert settings._url is not None
            assert settings._async_url is not None

    def test_setup_local_urls_without_wal_mode(self) -> None:
        """Test local URL setup with WAL mode disabled."""
        with tempfile.TemporaryDirectory() as tmpdir:
            db_path = Path(tmpdir) / "test.db"
            settings = SqlSettings(
                database_url=f"sqlite:///{db_path}",
                wal_mode=False
            )

            assert settings._url is not None
            assert settings._async_url is not None

    def test_ssl_enabled_property_inherited(self) -> None:
        """Test that ssl_enabled property is inherited correctly."""
        settings = SqlSettings()
        # Should inherit from base class
        assert hasattr(settings, 'ssl_enabled')

    def test_tls_version_property_inherited(self) -> None:
        """Test that tls_version property is inherited correctly."""
        settings = SqlSettings()
        # Should inherit from base class
        assert hasattr(settings, 'tls_version')


class TestSqlAdapter:
    """Test the Sql adapter class."""

    @pytest.fixture
    def mock_config(self) -> Mock:
        """Create a mock config."""
        mock_config = Mock(spec=Config)
        mock_config.sql = Mock(spec=SqlSettings)
        mock_config.sql.database_url = "sqlite:///test.db"
        mock_config.sql.is_turso = False
        mock_config.sql.wal_mode = True
        mock_config.sql.ssl_enabled = False
        mock_config.debug = Mock()
        mock_config.debug.sql = False
        return mock_config

    @pytest.fixture
    def sqlite_adapter(self, mock_config: Mock) -> Sql:
        """Create a SQLite adapter with mock config."""
        adapter = Sql()
        adapter.config = mock_config
        adapter.logger = Mock()
        return adapter

    @pytest.mark.asyncio
    async def test_create_client_local_sqlite(self, sqlite_adapter: Sql) -> None:
        """Test creating client for local SQLite."""
        mock_config = sqlite_adapter.config
        mock_config.sql.is_turso = False
        mock_config.sql.database_url = "sqlite:///test.db"
        mock_config.sql._async_url = URL.create("sqlite+aiosqlite", database="test.db")

        with (
            patch("acb.adapters.sql.sqlite.create_async_engine") as mock_create_engine,
            patch("acb.adapters.sql.sqlite.Path") as mock_path,
            patch("acb.adapters.sql.sqlite.text") as mock_text,
        ):
            mock_engine = AsyncMock()
            mock_create_engine.return_value = mock_engine

            # Mock Path operations
            mock_path_instance = Mock()
            mock_path_instance.parent.exists.return_value = True
            mock_path.return_value = mock_path_instance

            engine = await sqlite_adapter._create_client()

            mock_create_engine.assert_called_once()
            assert engine == mock_engine

    @pytest.mark.asyncio
    async def test_create_client_turso_sqlite(self, sqlite_adapter: Sql) -> None:
        """Test creating client for Turso SQLite."""
        mock_config = sqlite_adapter.config
        mock_config.sql.is_turso = True
        mock_config.sql._async_url = URL.create("sqlite+libsql", database="test.db")
        mock_config.sql.ssl_enabled = True

        with (
            patch("acb.adapters.sql.sqlite.create_async_engine") as mock_create_engine,
        ):
            mock_engine = AsyncMock()
            mock_create_engine.return_value = mock_engine

            engine = await sqlite_adapter._create_client()

            mock_create_engine.assert_called_once()
            assert engine == mock_engine

    @pytest.mark.asyncio
    async def test_create_client_creates_directories(self, sqlite_adapter: Sql) -> None:
        """Test that client creation creates database directories."""
        mock_config = sqlite_adapter.config
        mock_config.sql.is_turso = False
        mock_config.sql.database_url = "sqlite:///test/subdir/test.db"
        mock_config.sql._async_url = URL.create("sqlite+aiosqlite", database="test/subdir/test.db")

        with (
            patch("acb.adapters.sql.sqlite.create_async_engine") as mock_create_engine,
            patch("acb.adapters.sql.sqlite.Path") as mock_path,
        ):
            mock_engine = AsyncMock()
            mock_create_engine.return_value = mock_engine

            # Mock Path operations
            mock_parent = Mock()
            mock_parent.exists.return_value = False
            mock_path_instance = Mock()
            mock_path_instance.parent = mock_parent
            mock_path.return_value = mock_path_instance

            await sqlite_adapter._create_client()

            # Check that mkdir was called to create parent directories
            mock_parent.mkdir.assert_called_once_with(parents=True, exist_ok=True)

    @pytest.mark.asyncio
    async def test_init_local_sqlite(self, sqlite_adapter: Sql) -> None:
        """Test initializing local SQLite adapter."""
        mock_config = sqlite_adapter.config
        mock_config.sql.is_turso = False
        mock_config.debug.sql = False

        with (
            patch.object(sqlite_adapter, "get_conn") as mock_get_conn,
            patch("acb.adapters.sql.sqlite.import_adapter") as mock_import_adapter,
            patch("acb.adapters.sql.sqlite.SQLModel") as mock_sqlmodel,
            patch("acb.adapters.sql.sqlite.log") as mock_log,
        ):
            # Mock connection context manager
            mock_conn = AsyncMock()
            mock_get_conn.return_value.__aenter__.return_value = mock_conn

            # Mock SQLModel metadata
            mock_metadata = Mock()
            mock_sqlmodel.metadata = mock_metadata

            await sqlite_adapter.init()

            # Check that metadata operations were called
            mock_metadata.drop_all.assert_called_once()
            mock_metadata.create_all.assert_called_once()
            mock_import_adapter.assert_called_once_with("models")

    @pytest.mark.asyncio
    async def test_init_turso_sqlite(self, sqlite_adapter: Sql) -> None:
        """Test initializing Turso SQLite adapter."""
        mock_config = sqlite_adapter.config
        mock_config.sql.is_turso = True
        mock_config.sql.ssl_enabled = True
        mock_config.debug.sql = False

        with (
            patch.object(sqlite_adapter, "get_conn") as mock_get_conn,
            patch("acb.adapters.sql.sqlite.import_adapter") as mock_import_adapter,
            patch("acb.adapters.sql.sqlite.SQLModel") as mock_sqlmodel,
            patch("acb.adapters.sql.sqlite.log") as mock_log,
        ):
            # Mock connection context manager
            mock_conn = AsyncMock()
            mock_get_conn.return_value.__aenter__.return_value = mock_conn

            # Mock SQLModel metadata
            mock_metadata = Mock()
            mock_sqlmodel.metadata = mock_metadata

            await sqlite_adapter.init()

            # Check logger was called with Turso message
            sqlite_adapter.logger.info.assert_called()
            assert "Turso" in sqlite_adapter.logger.info.call_args[0][0]

    @pytest.mark.asyncio
    async def test_init_with_debug_sql(self, sqlite_adapter: Sql) -> None:
        """Test initializing with debug SQL enabled."""
        mock_config = sqlite_adapter.config
        mock_config.sql.is_turso = False
        mock_config.debug.sql = True

        with (
            patch.object(sqlite_adapter, "get_conn") as mock_get_conn,
            patch("acb.adapters.sql.sqlite.import_adapter") as mock_import_adapter,
            patch("acb.adapters.sql.sqlite.SQLModel") as mock_sqlmodel,
            patch("acb.adapters.sql.sqlite.log") as mock_log,
            patch("acb.adapters.sql.sqlite.text") as mock_text,
        ):
            # Mock connection context manager
            mock_conn = AsyncMock()
            mock_get_conn.return_value.__aenter__.return_value = mock_conn

            # Mock PRAGMA result
            mock_pragma_result = Mock()
            mock_pragma_result.scalar.return_value = "wal"
            mock_conn.execute.return_value = mock_pragma_result

            # Mock SQLModel metadata
            mock_metadata = Mock()
            mock_sqlmodel.metadata = mock_metadata

            await sqlite_adapter.init()

            # Check that PRAGMA was executed
            mock_conn.execute.assert_called()

    @pytest.mark.asyncio
    async def test_init_handles_exceptions(self, sqlite_adapter: Sql) -> None:
        """Test that init handles exceptions properly."""
        mock_config = sqlite_adapter.config
        mock_config.sql.is_turso = False
        mock_config.debug.sql = False

        with (
            patch.object(sqlite_adapter, "get_conn") as mock_get_conn,
            patch("acb.adapters.sql.sqlite.import_adapter") as mock_import_adapter,
            patch("acb.adapters.sql.sqlite.SQLModel") as mock_sqlmodel,
            patch("acb.adapters.sql.sqlite.log") as mock_log,
        ):
            # Mock connection context manager
            mock_conn = AsyncMock()
            mock_get_conn.return_value.__aenter__.return_value = mock_conn

            # Mock SQLModel metadata
            mock_metadata = Mock()
            mock_sqlmodel.metadata = mock_metadata

            # Make drop_all raise an exception
            mock_metadata.drop_all.side_effect = Exception("Test error")

            with pytest.raises(Exception, match="Test error"):
                await sqlite_adapter.init()

            # Check that logger.exception was called
            sqlite_adapter.logger.exception.assert_called()


# Additional edge case tests
class TestSqlSettingsEdgeCases:
    """Test edge cases for SqlSettings."""

    def test_setup_turso_urls_with_secure_param(self) -> None:
        """Test Turso URL setup with secure parameter."""
        settings = SqlSettings(
            database_url="libsql://test.turso.io?secure=true",
            ssl_enabled=True
        )

        # Just ensure it doesn't raise an exception
        assert settings._async_url is not None

    def test_setup_turso_urls_with_custom_tls_version(self) -> None:
        """Test Turso URL setup with custom TLS version."""
        with patch.object(SqlBaseSettings, "__init__", lambda self, **kw: None):
            settings = SqlSettings.__new__(SqlSettings)
            settings.database_url = "libsql://test.turso.io"
            settings.ssl_enabled = True
            settings.tls_version = "TLSv1.3"
            settings.auth_token = None
            settings.connect_timeout = None

            # Just ensure it doesn't raise an exception
            settings._setup_turso_urls()
            assert settings._async_url is not None

    def test_setup_turso_urls_with_connect_timeout(self) -> None:
        """Test Turso URL setup with connect timeout."""
        with patch.object(SqlBaseSettings, "__init__", lambda self, **kw: None):
            settings = SqlSettings.__new__(SqlSettings)
            settings.database_url = "libsql://test.turso.io"
            settings.ssl_enabled = True
            settings.tls_version = "TLSv1.2"
            settings.auth_token = None
            settings.connect_timeout = 30.0

            # Just ensure it doesn't raise an exception
            settings._setup_turso_urls()
            assert settings._async_url is not None

    def test_https_turso_url(self) -> None:
        """Test HTTPS Turso URL handling."""
        settings = SqlSettings(database_url="https://test.turso.io")
        assert settings._is_turso_url() is True

    def test_turso_in_url(self) -> None:
        """Test Turso detection in URL."""
        settings = SqlSettings(database_url="sqlite://test-with-turso-in-name.db")
        # Should not be detected as Turso just because "turso" is in the name
        # This might actually be detected as Turso, but let's test the current behavior
        detected_as_turso = settings._is_turso_url()
        # The behavior might vary, but it shouldn't crash
