# #region Test.PreviewPromptHelpers [C:3] [TYPE Module] [SEMANTICS test, translate, preview, token, budget, helpers]
# @BRIEF Tests for preview_prompt_helpers.py — estimate_token_budget_for_rows, compute_build_token_metadata.
# @RELATION BINDS_TO -> [preview_prompt_helpers]
# @TEST_CONTRACT: estimate_token_budget_for_rows -> dict | adjusts rows based on token budget
# @TEST_CONTRACT: compute_build_token_metadata -> dict | returns token estimates with cost warning

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_helpers import (
    estimate_token_budget_for_rows,
    compute_build_token_metadata,
)


class TestEstimateTokenBudgetForRows:
    """estimate_token_budget_for_rows — adjust row count based on budget."""

    def test_no_adjustment_needed(self):
        """When budget fits, rows unchanged."""
        job = MagicMock()
        job.translation_column = "name"
        job.context_columns = []
        source_rows = [{"name": "hello"}, {"name": "world"}]

        with patch("src.plugins.translate.preview_prompt_helpers.estimate_token_budget") as mock_budget:
            mock_budget.return_value = {
                "batch_size_adjusted": 5,
                "estimated_input_tokens": 100,
                "estimated_output_tokens": 200,
                "batch_size": 2,
            }
            result = estimate_token_budget_for_rows(source_rows, ["ru"], job, None)

        assert result["actual_row_count"] == 2
        assert result["source_rows"] == source_rows
        assert result["token_budget"]["batch_size_adjusted"] == 5

    def test_adjustment_reduces_rows(self):
        """When budget is tight, rows are truncated to fit."""
        job = MagicMock()
        job.translation_column = "name"
        job.context_columns = []
        source_rows = [{"name": f"row{i}"} for i in range(10)]

        with patch("src.plugins.translate.preview_prompt_helpers.estimate_token_budget") as mock_budget:
            # First call: batch_size_adjusted smaller than actual
            # Second call: after truncation
            mock_budget.side_effect = [
                {
                    "batch_size_adjusted": 3,
                    "estimated_input_tokens": 1000,
                    "estimated_output_tokens": 2000,
                    "batch_size": 10,
                },
                {
                    "batch_size_adjusted": 3,
                    "estimated_input_tokens": 300,
                    "estimated_output_tokens": 600,
                    "batch_size": 3,
                },
            ]
            result = estimate_token_budget_for_rows(source_rows, ["ru", "de"], job, None)

        assert result["actual_row_count"] == 3
        assert len(result["source_rows"]) == 3
        assert result["token_budget"]["batch_size"] == 3

    def test_empty_rows(self):
        """Empty source rows handled."""
        job = MagicMock()
        job.translation_column = "name"
        job.context_columns = []

        with patch("src.plugins.translate.preview_prompt_helpers.estimate_token_budget") as mock_budget:
            mock_budget.return_value = {
                "batch_size_adjusted": 0,
                "estimated_input_tokens": 0,
                "estimated_output_tokens": 0,
                "batch_size": 0,
            }
            result = estimate_token_budget_for_rows([], ["ru"], job, None)

        assert result["actual_row_count"] == 0
        assert result["source_rows"] == []

    def test_provider_model_passed(self):
        """Provider model string passed to estimate_token_budget."""
        job = MagicMock()
        job.translation_column = "name"
        job.context_columns = []

        with patch("src.plugins.translate.preview_prompt_helpers.estimate_token_budget") as mock_budget:
            mock_budget.return_value = {
                "batch_size_adjusted": 2,
                "estimated_input_tokens": 100,
                "estimated_output_tokens": 200,
                "batch_size": 2,
            }
            result = estimate_token_budget_for_rows(
                [{"name": "hello"}], ["ru"], job, "gpt-4o-mini"
            )

        assert result["actual_row_count"] == 1
        # verify provider_model was passed (checked via call args)
        call_kwargs = mock_budget.call_args[1]
        assert call_kwargs["provider_info"] == "gpt-4o-mini"


class TestComputeBuildTokenMetadata:
    """compute_build_token_metadata — token estimation metadata."""

    def test_basic_metadata(self):
        """Returns all expected keys with estimates."""
        with patch("src.plugins.translate.preview_prompt_helpers.TokenEstimator") as MockTE:
            MockTE.estimate_prompt_tokens.return_value = 100
            MockTE.estimate_output_tokens.return_value = 200
            MockTE.estimate_cost.return_value = 0.0006
            MockTE.check_cost_warning.return_value = None

            result = compute_build_token_metadata(
                prompt="translate this",
                row_meta=[{"id": 1}],
                target_languages=["ru"],
                num_languages=1,
                dictionary_section="## Dictionary\nterm -> word",
                actual_row_count=5,
                sample_size=5,
            )

        assert result["prompt"] == "translate this"
        assert result["row_meta"] == [{"id": 1}]
        assert result["target_languages"] == ["ru"]
        assert result["num_languages"] == 1
        assert result["dictionary_section"] == "## Dictionary\nterm -> word"
        assert result["sample_prompt_tokens"] == 100
        assert result["sample_output_tokens"] == 200
        assert result["sample_total_tokens"] == 300
        assert result["sample_cost"] == 0.0006
        assert result["total_est_rows"] == 50  # sample_size * 10
        assert result["total_est_tokens"] > 0
        assert result["total_est_cost"] > 0
        assert result["cost_warning"] is None
        assert result["actual_row_count"] == 5

    def test_with_cost_warning(self):
        """Cost warning returned when sample exceeds threshold."""
        with patch("src.plugins.translate.preview_prompt_helpers.TokenEstimator") as MockTE:
            MockTE.estimate_prompt_tokens.return_value = 500
            MockTE.estimate_output_tokens.return_value = 2000
            MockTE.estimate_cost.return_value = 0.005
            MockTE.check_cost_warning.return_value = (
                "Large preview — estimated 2500 tokens, ~$0.0050 cost (across 2 languages)"
            )

            result = compute_build_token_metadata(
                prompt="translate all this text",
                row_meta=[{"id": 1}, {"id": 2}],
                target_languages=["ru", "de"],
                num_languages=2,
                dictionary_section="",
                actual_row_count=2,
                sample_size=100,
            )

        assert result["cost_warning"] is not None
        assert "Large preview" in result["cost_warning"]
        assert "2 languages" in result["cost_warning"]

    def test_empty_prompt(self):
        """Empty prompt handled by TokenEstimator."""
        with patch("src.plugins.translate.preview_prompt_helpers.TokenEstimator") as MockTE:
            MockTE.estimate_prompt_tokens.return_value = 0
            MockTE.estimate_output_tokens.return_value = 0
            MockTE.estimate_cost.return_value = 0.0
            MockTE.check_cost_warning.return_value = None

            result = compute_build_token_metadata(
                prompt="", row_meta=[], target_languages=[], num_languages=1,
                dictionary_section="", actual_row_count=0, sample_size=0,
            )

        assert result["sample_prompt_tokens"] == 0
        assert result["sample_output_tokens"] == 0
        assert result["sample_total_tokens"] == 0
        assert result["sample_cost"] == 0.0

    def test_total_est_replaces_row_count_in_prompt(self):
        """total_est_tokens substitutes actual_row_count with total_est_rows in prompt."""
        with patch("src.plugins.translate.preview_prompt_helpers.TokenEstimator") as MockTE:
            MockTE.estimate_prompt_tokens.side_effect = lambda p: len(p)
            MockTE.estimate_output_tokens.return_value = 200
            MockTE.estimate_cost.return_value = 0.001
            MockTE.check_cost_warning.return_value = None

            result = compute_build_token_metadata(
                prompt="Translate 5 rows",
                row_meta=[{"id": 1}],
                target_languages=["ru"],
                num_languages=1,
                dictionary_section="",
                actual_row_count=5,
                sample_size=5,
            )

            # Second call: prompt.replace("5", "50")
            assert MockTE.estimate_prompt_tokens.call_count == 2
# #endregion Test.PreviewPromptHelpers
