# #region TestSQLInsertService [C:3] [TYPE Module] [SEMANTICS test,translate,sql,insert]
# @BRIEF Tests for SQLInsertService — SQL generation, Superset/direct DB execution.
# @RELATION BINDS_TO -> [SQLInsertService]
import pytest
from unittest.mock import AsyncMock, MagicMock, patch

from src.models.translate import TranslationJob, TranslationRun, TranslationRecord
from src.plugins.translate.orchestrator_sql import SQLInsertService


# #region TestSQLInsertService [C:3] [TYPE Class]
@pytest.mark.asyncio
class TestSQLInsertService:
    """Verify SQLInsertService handles generation and execution."""

    def _make_service(self, db=None):
        if db is None:
            db = MagicMock()
        config = MagicMock()
        event_log = MagicMock()
        return SQLInsertService(db, config, event_log)

    def _make_job(self, **kwargs) -> TranslationJob:
        job = MagicMock(spec=TranslationJob)
        job.id = "job-1"
        job.target_column = "translated_text"
        job.translation_column = "source_text"
        job.target_languages = ["ru"]
        job.target_schema = "public"
        job.target_table = "translations"
        job.target_key_cols = []
        job.upsert_strategy = "INSERT"
        job.insert_method = "sqllab"
        job.connection_id = None
        job.environment_id = "env-1"
        job.source_dialect = "postgresql"
        job.database_dialect = None
        job.target_dialect = None
        for k, v in kwargs.items():
            setattr(job, k, v)
        return job

    def _make_run(self) -> TranslationRun:
        run = MagicMock(spec=TranslationRun)
        run.id = "run-1"
        return run

    async def test_no_records_returns_skipped(self):
        db = MagicMock()
        db.query.return_value.options.return_value.filter.return_value.all.return_value = []
        service = self._make_service(db)
        job = self._make_job()
        run = self._make_run()

        result = await service.generate_and_insert_sql(job, run)

        assert result["status"] == "skipped"
        assert result["reason"] == "no_records"

    async def test_sql_generation_failure_returns_failed(self):
        db = MagicMock()
        record = MagicMock(spec=TranslationRecord)
        record.target_sql = "test"
        db.query.return_value.options.return_value.filter.return_value.all.return_value = [record]

        service = self._make_service(db)
        job = self._make_job()
        run = self._make_run()

        with patch('src.plugins.translate.orchestrator_sql.SQLGenerator.generate',
                   side_effect=ValueError("Invalid SQL")):
            with patch('src.plugins.translate.orchestrator_sql.build_columns', return_value=["col1"]):
                with patch('src.plugins.translate.orchestrator_sql.build_rows', return_value=[{"col1": "val1"}]):
                    with patch('src.plugins.translate.orchestrator_sql.build_context_keys', return_value=[]):
                        result = await service.generate_and_insert_sql(job, run)

        assert result["status"] == "failed"
        assert "Invalid SQL" in result["error_message"]

    async def test_direct_db_connection_not_found(self):
        db = MagicMock()
        record = MagicMock(spec=TranslationRecord)
        record.target_sql = "test"
        db.query.return_value.options.return_value.filter.return_value.all.return_value = [record]

        service = self._make_service(db)
        job = self._make_job(insert_method="direct_db", connection_id="conn-1")
        run = self._make_run()

        with patch('src.plugins.translate.orchestrator_sql.SQLGenerator.generate',
                   return_value=("INSERT INTO ...", 1)):
            with patch('src.plugins.translate.orchestrator_sql.build_columns', return_value=["col1"]):
                with patch('src.plugins.translate.orchestrator_sql.build_rows', return_value=[{"col1": "val1"}]):
                    with patch('src.plugins.translate.orchestrator_sql.build_context_keys', return_value=[]):
                        with patch('src.plugins.translate.orchestrator_sql.ConnectionService') as mock_conn:
                            mock_conn.return_value.get_connection.return_value = None
                            result = await service.generate_and_insert_sql(job, run)

        assert result["status"] == "failed"
        assert "not found" in result["error_message"]

    async def test_resolve_dialect_fallback_to_default(self):
        service = self._make_service()
        job = self._make_job(database_dialect=None, target_dialect=None)

        with patch('src.plugins.translate.orchestrator_sql.SupersetSqlLabExecutor') as mock_exec:
            mock_exec.return_value.resolve_database_id = AsyncMock()
            mock_exec.return_value.get_database_backend = MagicMock(return_value=None)
            dialect = await service._resolve_dialect(job)

        assert dialect == "postgresql"

    async def test_superset_sqllab_exception_returns_failed(self):
        db = MagicMock()
        service = self._make_service(db)
        job = self._make_job()
        run = self._make_run()

        with patch('src.plugins.translate.orchestrator_sql.SupersetSqlLabExecutor') as mock_exec:
            instance = mock_exec.return_value
            instance.resolve_database_id = AsyncMock()
            instance.execute_and_poll = AsyncMock(side_effect=Exception("Superset API error"))

            result = await service._execute_superset_sqllab(job, run, "INSERT INTO ...")

        assert result["status"] == "failed"
        assert "Superset API error" in result["error_message"]

    async def test_direct_db_success(self):
        db = MagicMock()
        service = self._make_service(db)
        job = self._make_job(insert_method="direct_db", connection_id="conn-1")
        run = self._make_run()

        with patch('src.plugins.translate.orchestrator_sql.ConnectionService') as mock_conn_cls:
            mock_conn = MagicMock()
            mock_conn.name = "ch_test"
            mock_conn.dialect = "clickhouse"
            mock_conn.host = "localhost"
            mock_conn.port = 8123
            mock_conn.database = "test_db"
            mock_conn_cls.return_value.get_connection.return_value = mock_conn

            with patch('src.plugins.translate.orchestrator_sql.DbExecutor') as mock_exec_cls:
                mock_result = MagicMock()
                mock_result.success = True
                mock_result.rows_affected = 10
                mock_result.execution_time_ms = 50
                mock_exec_cls.return_value.execute_sql = AsyncMock(return_value=mock_result)

                result = await service._execute_direct_db(job, run, "INSERT INTO ...")

        assert result["status"] == "success"
        assert result["rows_affected"] == 10
        assert result["connection_name"] == "ch_test"
        assert run.connection_snapshot is not None
        assert run.insert_method == "direct_db"
        assert db.commit.called

    async def test_direct_db_execution_failure(self):
        db = MagicMock()
        service = self._make_service(db)
        job = self._make_job(insert_method="direct_db", connection_id="conn-1")
        run = self._make_run()

        with patch('src.plugins.translate.orchestrator_sql.ConnectionService') as mock_conn_cls:
            mock_conn = MagicMock()
            mock_conn.name = "ch_test"
            mock_conn.dialect = "clickhouse"
            mock_conn.host = "localhost"
            mock_conn.port = 8123
            mock_conn.database = "test_db"
            mock_conn_cls.return_value.get_connection.return_value = mock_conn

            with patch('src.plugins.translate.orchestrator_sql.DbExecutor') as mock_exec_cls:
                mock_result = MagicMock()
                mock_result.success = False
                mock_result.error = "DB timeout"
                mock_result.execution_time_ms = 5000
                mock_exec_cls.return_value.execute_sql = AsyncMock(return_value=mock_result)

                result = await service._execute_direct_db(job, run, "INSERT INTO ...")

        assert result["status"] == "failed"
        assert "DB timeout" in result["error_message"]
        assert result["execution_time_ms"] == 5000

    async def test_resolve_dialect_direct_db_connection(self):
        service = self._make_service()
        job = self._make_job(insert_method="direct_db", connection_id="conn-1")

        with patch('src.plugins.translate.orchestrator_sql.ConnectionService') as mock_conn_cls:
            mock_conn = MagicMock()
            mock_conn.dialect = "clickhouse"
            mock_conn_cls.return_value.get_connection.return_value = mock_conn

            dialect = await service._resolve_dialect(job)
            assert dialect == "clickhouse"

    async def test_empty_columns_fallback(self):
        db = MagicMock()
        record = MagicMock()
        record.target_sql = "test"
        db.query.return_value.options.return_value.filter.return_value.all.return_value = [record]

        service = self._make_service(db)
        job = self._make_job(target_column=None, translation_column="source_text")
        run = self._make_run()

        with patch('src.plugins.translate.orchestrator_sql.SQLGenerator.generate',
                   return_value=("INSERT INTO ... VALUES ('test')", 1)) as mock_gen:
            with patch('src.plugins.translate.orchestrator_sql.build_columns', return_value=[]):
                with patch('src.plugins.translate.orchestrator_sql.build_rows', return_value=[]):
                    with patch('src.plugins.translate.orchestrator_sql.build_context_keys', return_value=[]):
                        with patch.object(service, '_resolve_dialect', AsyncMock(return_value="postgresql")):
                            with patch.object(service, '_execute_superset_sqllab', AsyncMock(return_value={"status": "success"})):
                                result = await service.generate_and_insert_sql(job, run)

        assert result["status"] == "success"
        mock_gen.assert_called_once()
# #endregion TestSQLInsertService
