# #region Test.RunSourceFetcher [C:3] [TYPE Module] [SEMANTICS test, translate, source, fetch]
# @BRIEF Tests for _run_source.py — fetch_source_rows, _extract_chart_data_rows.
# @RELATION BINDS_TO -> [_run_source]
# @TEST_EDGE: superset_datasource_fetch -> fetch_source_rows returns rows from Superset API
# @TEST_EDGE: empty_datasource -> fallback to preview session
# @TEST_EDGE: no_session -> returns empty list
import pytest
from unittest.mock import AsyncMock, MagicMock, patch
from datetime import UTC, datetime

from sqlalchemy import create_engine, event
from sqlalchemy.orm import Session, sessionmaker

from src.models.translate import (
    Base, TranslationJob, TranslationPreviewSession, TranslationPreviewRecord,
)
from src.plugins.translate._run_source import _extract_chart_data_rows


def make_session():
    engine = create_engine("sqlite:///:memory:")
    @event.listens_for(engine, "connect")
    def _set_sqlite_pragma(dbapi_connection, connection_record):
        cursor = dbapi_connection.cursor()
        cursor.execute("PRAGMA foreign_keys=ON")
        cursor.close()
    Base.metadata.create_all(bind=engine)
    session = sessionmaker(bind=engine)()
    return session, engine


# #region TestExtractChartDataRows [C:1] [TYPE Class]
class TestExtractChartDataRows:
    """_extract_chart_data_rows — extract data rows from Superset chart data API response."""

    def test_result_list_with_data(self):
        """result is a list with item containing data key."""
        response = {"result": [{"data": [{"x": 1}, {"x": 2}]}]}
        assert _extract_chart_data_rows(response) == [{"x": 1}, {"x": 2}]

    def test_result_dict_with_data(self):
        """result is a dict with data key."""
        response = {"result": {"data": [{"x": 1}]}}
        assert _extract_chart_data_rows(response) == [{"x": 1}]

    def test_fallback_to_response_data(self):
        """Fallback to response.data."""
        response = {"data": [{"x": 1}]}
        assert _extract_chart_data_rows(response) == [{"x": 1}]

    def test_result_list_no_data(self):
        """When result is a list with no data, return it."""
        response = {"result": [{"x": 1}]}
        assert _extract_chart_data_rows(response) == [{"x": 1}]

    def test_empty_result_list(self):
        """Empty list returns empty list."""
        assert _extract_chart_data_rows({"result": []}) == []

    def test_empty_response(self):
        """No recognizable fields returns empty list."""
        assert _extract_chart_data_rows({"foo": "bar"}) == []

# #endregion TestExtractChartDataRows


# #region TestFetchSourceRows [C:3] [TYPE Class]
class TestFetchSourceRows:
    """fetch_source_rows — fetch source rows from Superset datasource or preview."""

    @pytest.mark.asyncio
    async def test_no_job_returns_empty(self):
        """No matching job -> empty list."""
        session, engine = make_session()
        try:
            config_manager = MagicMock()
            from src.plugins.translate._run_source import fetch_source_rows
            result = await fetch_source_rows(session, config_manager, "nonexistent-job", "run-1")
            assert result == []
        finally:
            session.close()
            Base.metadata.drop_all(bind=engine)

    @pytest.mark.asyncio
    async def test_job_without_datasource_preview_session(self):
        """Job with no datasource, but has preview session."""
        session, engine = make_session()
        try:
            job = TranslationJob(id="job-1", name="Test", status="ACTIVE",
                                 source_dialect="en", target_dialect="fr",
                                 source_datasource_id=None)
            session.add(job)
            sess = TranslationPreviewSession(id="ps-1", job_id="job-1", status="APPLIED",
                                             created_at=datetime.now(UTC))
            session.add(sess)
            session.flush()  # Ensure TranslationPreviewSession is persisted before TranslationPreviewRecord
            rec = TranslationPreviewRecord(id="rec-1", session_id="ps-1", status="APPROVED",
                                           source_object_id="row1", source_sql="hello",
                                           source_object_name="Row 1")
            session.add(rec)
            session.commit()

            config_manager = MagicMock()
            from src.plugins.translate._run_source import fetch_source_rows
            result = await fetch_source_rows(session, config_manager, "job-1", "run-1")
            assert len(result) == 1
            assert result[0]["source_text"] == "hello"
            # approved_translation should be the target_sql (None here since status=APPROVED but target_sql not set)
            assert result[0]["approved_translation"] is None
        finally:
            session.close()
            Base.metadata.drop_all(bind=engine)

    @pytest.mark.asyncio
    async def test_no_preview_session_returns_empty(self):
        """Job without datasource, no preview session -> empty list."""
        session, engine = make_session()
        try:
            job = TranslationJob(id="job-2", name="Test", status="ACTIVE",
                                 source_dialect="en", target_dialect="fr",
                                 source_datasource_id=None)
            session.add(job)
            session.commit()

            config_manager = MagicMock()
            from src.plugins.translate._run_source import fetch_source_rows
            result = await fetch_source_rows(session, config_manager, "job-2", "run-2")
            assert result == []
        finally:
            session.close()
            Base.metadata.drop_all(bind=engine)

    @pytest.mark.asyncio
    async def test_superset_datasource_success(self):
        """Job with datasource, successful fetch from Superset."""
        session, engine = make_session()
        try:
            job = TranslationJob(id="job-3", name="Test", status="ACTIVE",
                                 source_dialect="en", target_dialect="fr",
                                 source_datasource_id=42, environment_id="env-1",
                                 translation_column="text")
            session.add(job)
            session.commit()

            config_manager = MagicMock()
            config_manager.get_environments.return_value = [
                MagicMock(id="env-1")
            ]

            mock_client = MagicMock()
            mock_client.get_dataset_detail = AsyncMock(return_value={"id": 42})
            mock_client.build_dataset_preview_query_context = MagicMock(return_value={
                "queries": [{}], "form_data": {}
            })
            mock_client.network.request = AsyncMock(return_value={
                "result": [{"data": [{"text": "hello"}, {"text": "world"}]}]
            })

            from src.plugins.translate._run_source import fetch_source_rows

            async def mock_get_superset_client(*args, **kwargs):
                return mock_client
            with patch('src.core.utils.client_registry.get_superset_client',
                       new=mock_get_superset_client):
                result = await fetch_source_rows(session, config_manager, "job-3", "run-3")
            assert len(result) == 2
            assert result[0]["source_text"] == "hello"
            assert result[1]["source_text"] == "world"
            assert result[0]["source_data"] == {"text": "hello"}
        finally:
            session.close()
            Base.metadata.drop_all(bind=engine)

    @pytest.mark.asyncio
    async def test_superset_fetch_falls_back_to_preview(self):
        """Superset fetch fails, falls back to preview session."""
        session, engine = make_session()
        try:
            job = TranslationJob(id="job-4", name="Test", status="ACTIVE",
                                 source_dialect="en", target_dialect="fr",
                                 source_datasource_id=42, environment_id="env-1",
                                 translation_column="text")
            session.add(job)
            sess = TranslationPreviewSession(id="ps-4", job_id="job-4", status="APPLIED",
                                             created_at=datetime.now(UTC))
            session.add(sess)
            session.flush()  # Ensure TranslationPreviewSession is persisted before TranslationPreviewRecord
            rec = TranslationPreviewRecord(id="rec-4", session_id="ps-4", status="PENDING",
                                           source_object_id="row1", source_sql="fallback text",
                                           source_object_name="Row 1")
            session.add(rec)
            session.commit()

            config_manager = MagicMock()
            config_manager.get_environments.return_value = [
                MagicMock(id="env-1")
            ]

            mock_client = AsyncMock()
            mock_client.get_dataset_detail.side_effect = Exception("API error")

            from src.plugins.translate._run_source import fetch_source_rows

            async def mock_get_superset_client(*args, **kwargs):
                return mock_client
            with patch('src.core.utils.client_registry.get_superset_client',
                       new=mock_get_superset_client):
                result = await fetch_source_rows(session, config_manager, "job-4", "run-4")
            assert len(result) == 1
            assert result[0]["source_text"] == "fallback text"
        finally:
            session.close()
            Base.metadata.drop_all(bind=engine)

    @pytest.mark.asyncio
    async def test_superset_empty_rows_falls_back(self):
        """Superset returns empty rows, falls back to preview."""
        session, engine = make_session()
        try:
            job = TranslationJob(id="job-5", name="Test", status="ACTIVE",
                                 source_dialect="en", target_dialect="fr",
                                 source_datasource_id=42, environment_id="env-1",
                                 translation_column="text")
            session.add(job)
            sess = TranslationPreviewSession(id="ps-5", job_id="job-5", status="APPLIED",
                                             created_at=datetime.now(UTC))
            session.add(sess)
            session.flush()  # Ensure TranslationPreviewSession is persisted before TranslationPreviewRecord
            rec = TranslationPreviewRecord(id="rec-5", session_id="ps-5", status="APPROVED",
                                           source_object_id="row1", source_sql="fb text",
                                           source_object_name="Row 1")
            session.add(rec)
            session.commit()

            config_manager = MagicMock()
            config_manager.get_environments.return_value = [
                MagicMock(id="env-1")
            ]

            mock_client = MagicMock()
            mock_client.get_dataset_detail = AsyncMock(return_value={"id": 42})
            mock_client.build_dataset_preview_query_context = MagicMock(return_value={
                "queries": [{}], "form_data": {}
            })
            mock_client.network.request = AsyncMock(return_value={"result": []})

            from src.plugins.translate._run_source import fetch_source_rows

            async def mock_get_superset_client(*args, **kwargs):
                return mock_client
            with patch('src.core.utils.client_registry.get_superset_client',
                       new=mock_get_superset_client):
                result = await fetch_source_rows(session, config_manager, "job-5", "run-5")
            assert len(result) == 1
            assert result[0]["source_text"] == "fb text"
        finally:
            session.close()
            Base.metadata.drop_all(bind=engine)

    @pytest.mark.asyncio
    async def test_superset_no_env_config_falls_back(self):
        """No matching environment config -> falls back to preview."""
        session, engine = make_session()
        try:
            job = TranslationJob(id="job-6", name="Test", status="ACTIVE",
                                 source_dialect="en", target_dialect="fr",
                                 source_datasource_id=42, environment_id="nonexistent",
                                 translation_column="text")
            session.add(job)
            session.commit()

            config_manager = MagicMock()
            config_manager.get_environments.return_value = [
                MagicMock(id="other-env")
            ]

            from src.plugins.translate._run_source import fetch_source_rows
            result = await fetch_source_rows(session, config_manager, "job-6", "run-6")
            # No preview session, falls back to empty
            assert result == []
        finally:
            session.close()
            Base.metadata.drop_all(bind=engine)

# #endregion TestFetchSourceRows
# #endregion Test.RunSourceFetcher
