from pathlib import Path

import pytest

from cli.runtime.base import RuntimeEnvironmentService
from cli.runtime.db import DatabaseRuntimeService
from cli.runtime.db.gateway import DatabaseInfrastructureGateway
from cli.runtime.db.support import DatabaseRevisionSupport


class FakeRuntimeEnvironment(RuntimeEnvironmentService):
    """
    模拟运行时环境服务。
    """

    @staticmethod
    def get_backend_dir() -> str:
        """
        返回固定后端目录。

        :return: 固定目录
        """
        return '/tmp/shierfeng-backend'


def test_database_revision_support_serializes_revision() -> None:
    """
    校验数据库迁移版本支持对象会序列化 Alembic revision。

    :return: None
    """

    class FakeRevision:
        """
        模拟 Alembic revision 对象。
        """

        revision = '202605110001'
        down_revision = ('202605100001',)
        branch_labels = {'main'}
        dependencies = None
        doc = ' demo revision '
        path = Path('/tmp/revision.py')

    support = DatabaseRevisionSupport(DatabaseInfrastructureGateway(), FakeRuntimeEnvironment())

    payload = support.serialize_revision(FakeRevision())

    assert payload == {
        'revision': '202605110001',
        'downRevisions': ['202605100001'],
        'branchLabels': ['main'],
        'dependsOn': [],
        'doc': 'demo revision',
        'path': '/tmp/revision.py',
    }


def test_database_runtime_service_upgrade_dry_run_returns_command_payload() -> None:
    """
    校验数据库运行时 dry-run 升级会返回命令预览结果。

    :return: None
    """
    service = DatabaseRuntimeService(runtime_environment=FakeRuntimeEnvironment())

    payload = service.upgrade_database('head', dry_run=True)

    assert payload['ok'] is True
    assert payload['dryRun'] is True
    assert payload['cwd'] == '/tmp/shierfeng-backend'
    assert payload['command'] == ['alembic', '-c', '/tmp/shierfeng-backend/alembic.ini', 'upgrade', 'head']


@pytest.mark.asyncio
async def test_database_runtime_service_ping_database_returns_failure() -> None:
    """
    校验数据库运行时在连接异常时会返回失败结果。

    :return: None
    """

    class FakeConnection:
        """
        模拟数据库连接对象。
        """

        async def __aenter__(self) -> 'FakeConnection':
            raise RuntimeError('db boom')

        async def __aexit__(
            self,
            exc_type: type[BaseException] | None,
            exc: BaseException | None,
            tb: object,
        ) -> None:
            del exc_type, exc, tb

    class FakeEngine:
        """
        模拟数据库引擎。
        """

        @staticmethod
        def connect() -> FakeConnection:
            """
            返回模拟连接对象。

            :return: 模拟连接对象
            """
            return FakeConnection()

        @staticmethod
        async def dispose() -> None:
            """
            释放引擎资源。

            :return: None
            """

    gateway = DatabaseInfrastructureGateway()
    service = DatabaseRuntimeService(
        runtime_environment=FakeRuntimeEnvironment(),
        infrastructure_gateway=gateway,
    )

    def _fake_get_async_db_engine_factory() -> object:
        def _factory(*, echo: bool = False) -> FakeEngine:
            del echo
            return FakeEngine()

        return _factory

    def _fake_get_sqlalchemy_text() -> object:
        return lambda sql: sql

    object.__setattr__(gateway, 'get_async_db_engine_factory', _fake_get_async_db_engine_factory)
    object.__setattr__(gateway, 'get_sqlalchemy_text', _fake_get_sqlalchemy_text)

    payload = await service.ping_database()

    assert payload['ok'] is False
    assert payload['message'] == '数据库连接失败'
    assert 'db boom' in payload['error']
