"""
Tests for services/quality/pipeline.py — the L1 wrapper.
"""
import asyncio
import os
import pytest
from unittest.mock import AsyncMock, MagicMock, patch

from services.quality.pipeline import run_l1_check
from services.quality.llm_judge import L1Result, LLMJudge


class TestRunL1Check:
    """Test the defensive wrapper around the LLM judge."""

    def test_too_few_chunks_returns_skip(self):
        sources = ["a", "b", "c"]  # only 3 chunks
        translations = ["x", "y", "z"]
        result = asyncio.run(run_l1_check(
            sources, translations, "fr",
            max_samples=5, min_chunks=10,
        ))
        assert result.verdict == "skip"

    def test_no_judge_configured_returns_skip(self, monkeypatch):
        monkeypatch.delenv("L1_JUDGE_API_KEY", raising=False)
        sources = [f"source {i}" for i in range(15)]
        translations = [f"translation {i}" for i in range(15)]
        result = asyncio.run(run_l1_check(
            sources, translations, "fr",
            max_samples=5, min_chunks=10,
        ))
        assert result.verdict == "skip"
        assert "judge" in result.error.lower() or "configured" in result.error.lower() or result.error == "not_run"

    def test_skips_l0_failures(self):
        """Chunks that L0 already flagged should be excluded from the sample."""
        # All chunks are long enough to be sampled. Use unique markers per
        # index so substring checks don't have false positives (e.g.
        # "source1" being a substring of "source10").
        sources = [f"srcidx{i:02d} with enough length" for i in range(15)]
        translations = [f"transidx{i:02d} avec longueur suffisante" for i in range(15)]

        # Mock judge that records what it received
        mock_judge = MagicMock(spec=LLMJudge)
        mock_judge.judge_batch = AsyncMock(return_value=L1Result(
            verdict="pass", chunks_evaluated=5, chunks_passed=5,
            chunks_failed=0, failure_rate=0.0,
        ))

        # Mark indices 01, 03, 05, 07, 09, 11, 13 as L0 failures
        l0_failures = {1, 3, 5, 7, 9, 11, 13}

        result = asyncio.run(run_l1_check(
            sources, translations, "fr",
            l0_failed_indices=l0_failures,
            max_samples=5, min_chunks=10,
            judge=mock_judge,
        ))

        # The mock was called with 5 pairs
        call_args = mock_judge.judge_batch.call_args
        pairs = call_args[0][0]
        # The pairs should NOT include any source from the L0-failed indices
        for src, _trans in pairs:
            for failed_idx in l0_failures:
                marker = f"srcidx{failed_idx:02d}"
                assert marker not in src, (
                    f"L0-failed source {failed_idx} was sent to L1: {src}"
                )

    def test_passes_log_only_flag(self):
        """The log_only flag should be passed through to the LLMJudge result."""
        mock_judge = MagicMock(spec=LLMJudge)
        mock_judge.judge_batch = AsyncMock(return_value=L1Result(
            verdict="pass", chunks_evaluated=1, chunks_passed=1,
            chunks_failed=0, failure_rate=0.0,
        ))

        sources = [f"long enough source {i} " * 3 for i in range(15)]
        translations = [f"long enough translation {i} " * 3 for i in range(15)]

        asyncio.run(run_l1_check(
            sources, translations, "fr",
            max_samples=5, min_chunks=10,
            judge=mock_judge,
            log_only=True,
        ))
        # log_only doesn't affect the call, just the downstream decision

    def test_never_raises_on_judge_error(self):
        """If the judge itself raises, run_l1_check should catch it."""
        mock_judge = MagicMock(spec=LLMJudge)
        mock_judge.judge_batch = AsyncMock(side_effect=Exception("boom"))

        sources = [f"long enough source {i} " * 3 for i in range(15)]
        translations = [f"long enough translation {i} " * 3 for i in range(15)]

        # Should NOT raise
        result = asyncio.run(run_l1_check(
            sources, translations, "fr",
            max_samples=5, min_chunks=10,
            judge=mock_judge,
        ))
        assert result is not None
        # verdict is either "skip" or an error one — never crashes the call

    def test_target_lang_name_for_known_lang(self):
        """Verify the LANG_NAMES mapping is used."""
        mock_judge = MagicMock(spec=LLMJudge)
        mock_judge.judge_batch = AsyncMock(return_value=L1Result(
            verdict="pass", chunks_evaluated=1, chunks_passed=1,
            chunks_failed=0, failure_rate=0.0,
        ))

        sources = [f"long enough source {i} " * 3 for i in range(15)]
        translations = [f"long enough translation {i} " * 3 for i in range(15)]

        asyncio.run(run_l1_check(
            sources, translations, "fr",
            max_samples=5, min_chunks=10,
            judge=mock_judge,
        ))
        call_args = mock_judge.judge_batch.call_args
        # target_lang_name is the 3rd positional arg
        target_lang_name = call_args[0][2]
        assert target_lang_name == "French"
