# #region Test.PreviewPromptBuilder [C:3] [TYPE Module] [SEMANTICS test, translate, preview, prompt, builder]
# @BRIEF Tests for preview_prompt_builder.py — PreviewPromptBuilder.build_prompt_from_rows.
# @RELATION BINDS_TO -> [PreviewPromptBuilder]
# @TEST_CONTRACT: build_prompt_from_rows -> dict | builds LLM prompt with glossary
# @TEST_CONTRACT: estimate_token_budget_for_rows -> dict | delegates to helper
# @TEST_EDGE: dictionary_matches
# @TEST_EDGE: no_dictionary_matches
# @TEST_EDGE: custom_prompt_template

import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent / "src"))

import os
os.environ.setdefault("SECRET_KEY", "test-secret-key-for-testing")
os.environ.setdefault("DATABASE_URL", "sqlite:///:memory:")
os.environ.setdefault("AUTH_DATABASE_URL", "sqlite:///:memory:")

from unittest.mock import MagicMock, patch

import pytest

from src.plugins.translate.preview_prompt_builder import PreviewPromptBuilder


class TestBuildPromptFromRows:
    """PreviewPromptBuilder.build_prompt_from_rows — builds prompt with dictionary glossary."""

    def _make_job(self, **kwargs):
        job = MagicMock()
        job.id = "job-1"
        job.source_dialect = "postgresql"
        job.target_dialect = "clickhouse"
        job.target_languages = ["ru", "de"]
        job.translation_column = "name"
        job.context_columns = ["description"]
        for k, v in kwargs.items():
            setattr(job, k, v)
        return job

    def _make_db(self):
        return MagicMock()

    def test_build_prompt_basic(self):
        """Builds prompt with row data and no dictionary section."""
        db = self._make_db()
        job = self._make_job()
        source_rows = [
            {"name": "hello", "description": "greeting"},
            {"name": "world", "description": "earth"},
        ]

        with patch(
            "src.plugins.translate.preview_prompt_builder.DictionaryManager.filter_for_batch"
        ) as mock_filter:
            mock_filter.return_value = []

            with patch(
                "src.plugins.translate.preview_prompt_builder.render_prompt"
            ) as mock_render:
                mock_render.return_value = "rendered prompt"

                with patch(
                    "src.plugins.translate.preview_prompt_builder.compute_build_token_metadata"
                ) as mock_meta:
                    mock_meta.return_value = {
                        "prompt": "rendered prompt", "row_meta": [],
                        "target_languages": ["ru", "de"], "num_languages": 2,
                        "dictionary_section": "", "sample_prompt_tokens": 100,
                        "sample_output_tokens": 200, "sample_total_tokens": 300,
                        "sample_cost": 0.001, "total_est_rows": 50, "total_est_tokens": 500,
                        "total_est_cost": 0.01, "cost_warning": None, "actual_row_count": 2,
                    }

                    builder = PreviewPromptBuilder(db)
                    result = builder.build_prompt_from_rows(job, source_rows, sample_size=5)

        assert result["prompt"] == "rendered prompt"
        assert result["sample_prompt_tokens"] == 100
        mock_filter.assert_called_once()
        mock_render.assert_called_once()

    def test_build_prompt_with_dictionary(self):
        """Dictionary matches produce glossary section."""
        db = self._make_db()
        job = self._make_job()
        source_rows = [{"name": "hello", "description": "greeting"}]

        with patch(
            "src.plugins.translate.preview_prompt_builder.DictionaryManager.filter_for_batch"
        ) as mock_filter:
            mock_filter.return_value = [
                {"source_term": "hello", "target_term": "привет", "context_notes": "informal"},
                {"source_term": "world", "target_term": "мир", "context_notes": None},
            ]

            with patch(
                "src.plugins.translate.preview_prompt_builder.render_prompt"
            ) as mock_render:
                mock_render.side_effect = lambda template, ctx: ctx.get("dictionary_section", "")

                with patch(
                    "src.plugins.translate.preview_prompt_builder.compute_build_token_metadata"
                ) as mock_meta:
                    mock_meta.return_value = {
                        "prompt": "", "row_meta": [], "target_languages": ["ru", "de"],
                        "num_languages": 2, "dictionary_section": "...",
                        "sample_prompt_tokens": 0, "sample_output_tokens": 0,
                        "sample_total_tokens": 0, "sample_cost": 0.0,
                        "total_est_rows": 10, "total_est_tokens": 0, "total_est_cost": 0.0,
                        "cost_warning": None, "actual_row_count": 1,
                    }

                    builder = PreviewPromptBuilder(db)
                    result = builder.build_prompt_from_rows(job, source_rows, sample_size=1)

        # Verify dictionary was included in render
        mock_render.assert_called_once()
        variables = mock_render.call_args[0][1]
        assert "Terminology dictionary" in variables["dictionary_section"]
        assert "hello" in variables["dictionary_section"]
        assert "привет" in variables["dictionary_section"]
        assert "informal" in variables["dictionary_section"]

    def test_build_prompt_no_context_columns(self):
        """Job without context columns still works."""
        db = self._make_db()
        job = self._make_job(context_columns=None)
        source_rows = [{"name": "hello"}]

        with patch(
            "src.plugins.translate.preview_prompt_builder.DictionaryManager.filter_for_batch"
        ) as mock_filter:
            mock_filter.return_value = []

            with patch(
                "src.plugins.translate.preview_prompt_builder.render_prompt"
            ) as mock_render:
                mock_render.return_value = "prompt"
                with patch(
                    "src.plugins.translate.preview_prompt_builder.compute_build_token_metadata"
                ) as mock_meta:
                    mock_meta.return_value = {"prompt": "prompt"}

                    builder = PreviewPromptBuilder(db)
                    result = builder.build_prompt_from_rows(job, source_rows, sample_size=1)

        assert result["prompt"] == "prompt"

    def test_custom_prompt_template(self):
        """Custom prompt template overrides default."""
        db = self._make_db()
        job = self._make_job()
        source_rows = [{"name": "hello"}]
        custom_template = "Custom: {source_language} -> {target_languages}"

        with patch(
            "src.plugins.translate.preview_prompt_builder.DictionaryManager.filter_for_batch"
        ) as mock_filter:
            mock_filter.return_value = []

            with patch(
                "src.plugins.translate.preview_prompt_builder.render_prompt"
            ) as mock_render:
                mock_render.return_value = "custom rendered"
                with patch(
                    "src.plugins.translate.preview_prompt_builder.compute_build_token_metadata"
                ) as mock_meta:
                    mock_meta.return_value = {"prompt": "custom rendered"}

                    builder = PreviewPromptBuilder(db)
                    result = builder.build_prompt_from_rows(
                        job, source_rows, sample_size=1, prompt_template=custom_template,
                    )

        # Verify custom template was used
        mock_render.assert_called_once()
        assert mock_render.call_args[0][0] == custom_template

    def test_default_prompt_template(self):
        """Default prompt template when none provided."""
        db = self._make_db()
        job = self._make_job()
        source_rows = [{"name": "hello"}]

        with patch(
            "src.plugins.translate.preview_prompt_builder.DictionaryManager.filter_for_batch"
        ) as mock_filter:
            mock_filter.return_value = []

            with patch(
                "src.plugins.translate.preview_prompt_builder.render_prompt"
            ) as mock_render:
                mock_render.return_value = "default rendered"
                with patch(
                    "src.plugins.translate.preview_prompt_builder.compute_build_token_metadata"
                ) as mock_meta:
                    mock_meta.return_value = {"prompt": "default rendered"}
                    from src.plugins.translate.preview_constants import DEFAULT_PREVIEW_PROMPT_TEMPLATE

                    builder = PreviewPromptBuilder(db)
                    result = builder.build_prompt_from_rows(job, source_rows, sample_size=1)

        assert mock_render.call_args[0][0] == DEFAULT_PREVIEW_PROMPT_TEMPLATE

    def test_string_target_languages_converted(self):
        """String target_languages converted to list."""
        db = self._make_db()
        job = self._make_job(target_languages="ru")  # string, not list
        source_rows = [{"name": "hello"}]

        with patch(
            "src.plugins.translate.preview_prompt_builder.DictionaryManager.filter_for_batch"
        ) as mock_filter:
            mock_filter.return_value = []

            with patch(
                "src.plugins.translate.preview_prompt_builder.render_prompt"
            ) as mock_render:
                mock_render.return_value = "prompt"
                with patch(
                    "src.plugins.translate.preview_prompt_builder.compute_build_token_metadata"
                ) as mock_meta:
                    mock_meta.return_value = {"prompt": "prompt"}

                    builder = PreviewPromptBuilder(db)
                    result = builder.build_prompt_from_rows(job, source_rows, sample_size=1)

        # Verify target_languages was converted to list
        mock_render.assert_called_once()
        variables = mock_render.call_args[0][1]
        assert "ru" in variables["target_languages"]

    def test_empty_source_rows(self):
        """Empty source rows handled."""
        db = self._make_db()
        job = self._make_job()
        source_rows = []

        with patch(
            "src.plugins.translate.preview_prompt_builder.DictionaryManager.filter_for_batch"
        ) as mock_filter:
            mock_filter.return_value = []

            with patch(
                "src.plugins.translate.preview_prompt_builder.render_prompt"
            ) as mock_render:
                mock_render.return_value = "prompt"
                with patch(
                    "src.plugins.translate.preview_prompt_builder.compute_build_token_metadata"
                ) as mock_meta:
                    mock_meta.return_value = {"prompt": "prompt"}

                    builder = PreviewPromptBuilder(db)
                    result = builder.build_prompt_from_rows(job, source_rows, sample_size=5)

        assert result["prompt"] == "prompt"

    def test_json_rows_format(self):
        """Rows are serialized to JSON in prompt context."""
        db = self._make_db()
        job = self._make_job()
        source_rows = [{"name": "hello", "description": "greeting"}]

        with patch(
            "src.plugins.translate.preview_prompt_builder.DictionaryManager.filter_for_batch"
        ) as mock_filter:
            mock_filter.return_value = []

            with patch(
                "src.plugins.translate.preview_prompt_builder.render_prompt"
            ) as mock_render:
                mock_render.return_value = "prompt"
                with patch(
                    "src.plugins.translate.preview_prompt_builder.compute_build_token_metadata"
                ) as mock_meta:
                    mock_meta.return_value = {"prompt": "prompt"}

                    builder = PreviewPromptBuilder(db)
                    result = builder.build_prompt_from_rows(job, source_rows, sample_size=1)

        variables = mock_render.call_args[0][1]
        assert "row_id" in variables["rows_json"]
        assert "text" in variables["rows_json"]
        assert "hello" in variables["rows_json"]

    def test_row_meta_contains_context(self):
        """Row meta includes context_data from context columns."""
        db = self._make_db()
        job = self._make_job(context_columns=["description", "category"])
        source_rows = [{"name": "hello", "description": "greeting", "category": "common"}]

        with patch(
            "src.plugins.translate.preview_prompt_builder.DictionaryManager.filter_for_batch"
        ) as mock_filter:
            mock_filter.return_value = []

            with patch(
                "src.plugins.translate.preview_prompt_builder.render_prompt"
            ) as mock_render:
                mock_render.return_value = "prompt"
                with patch(
                    "src.plugins.translate.preview_prompt_builder.compute_build_token_metadata"
                ) as mock_meta:
                    mock_meta.return_value = {"prompt": "prompt"}

                    builder = PreviewPromptBuilder(db)
                    result = builder.build_prompt_from_rows(job, source_rows, sample_size=1)

        # Verify the row_context was passed to filter_for_batch
        mock_filter.assert_called_once()
        filter_kwargs = mock_filter.call_args[1]
        assert filter_kwargs["row_context"] == {"description": "greeting", "category": "common"}


class TestEstimateTokenBudgetForRows:
    """PreviewPromptBuilder.estimate_token_budget_for_rows — delegates to helper."""

    def test_delegates_to_helper(self):
        """Calls estimate_token_budget_for_rows helper."""
        db = MagicMock()
        job = MagicMock()
        source_rows = [{"name": "hello"}]

        with patch(
            "src.plugins.translate.preview_prompt_builder.estimate_token_budget_for_rows"
        ) as mock_helper:
            mock_helper.return_value = {"actual_row_count": 1, "source_rows": source_rows}

            builder = PreviewPromptBuilder(db)
            result = builder.estimate_token_budget_for_rows(
                source_rows, ["ru"], job, "gpt-4o-mini",
            )

        mock_helper.assert_called_once_with(
            source_rows=source_rows, target_languages=["ru"],
            job=job, provider_model="gpt-4o-mini",
        )
        assert result == {"actual_row_count": 1, "source_rows": source_rows}
# #endregion Test.PreviewPromptBuilder
