# #region Test.Api.TranslateRunEditRoutes [C:3] [TYPE Module] [SEMANTICS test,translate,run,edit,bulk,override,api]
# @BRIEF Tests for _run_edit_routes.py — language override, inline edit, bulk find-replace.
# @RELATION BINDS_TO -> [TranslateRunEditRoutesModule]
# @TEST_EDGE: override_record_not_found -> 404
# @TEST_EDGE: override_lang_entry_not_found -> 404
# @TEST_EDGE: override_generic_error -> 400
# @TEST_EDGE: inline_edit_value_error -> 400
# @TEST_EDGE: inline_edit_generic_error -> 400
# @TEST_EDGE: bulk_replace_value_error -> 400
# @TEST_EDGE: bulk_replace_generic_error -> 400

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

import sys
from pathlib import Path
from unittest.mock import MagicMock, patch

import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient

_src = str(Path(__file__).resolve().parent.parent.parent / "src")
if _src not in sys.path:
    sys.path.insert(0, _src)


def _make_client(overrides: dict | None = None) -> TestClient:
    from src.api.routes.translate._run_edit_routes import router
    from src.dependencies import (
        get_config_manager, get_current_user, get_db,
        has_permission, require_feature,
    )
    from src.schemas.auth import User, RoleSchema

    app = FastAPI()
    app.include_router(router)

    mock_user = User(
        id="user-1", username="admin", email="admin@x.com",
        auth_source="LOCAL",
        created_at=__import__("datetime").datetime.now(),
        roles=[RoleSchema(id="r1", name="Admin", description="", permissions=[])],
    )

    defaults = {
        get_current_user: lambda: mock_user,
        get_config_manager: lambda: MagicMock(),
        get_db: lambda: MagicMock(),
        has_permission: lambda *a, **kw: lambda: None,
        require_feature: lambda *a, **kw: lambda: None,
    }
    if overrides:
        defaults.update(overrides)
    for dep, mock_fn in defaults.items():
        app.dependency_overrides[dep] = mock_fn
    return TestClient(app)


def _mock_record(id="rec-1", run_id="run-1"):
    r = MagicMock()
    r.id = id
    r.run_id = run_id
    return r


def _mock_lang_entry(record_id="rec-1", language_code="es"):
    e = MagicMock()
    e.record_id = record_id
    e.language_code = language_code
    e.source_language_detected = "en"
    e.language_overridden = False
    e.needs_review = False
    # Explicit None for string fields to avoid MagicMock auto-creation
    e.translated_value = None
    e.user_edit = None
    e.final_value = None
    e.status = "pending"
    return e


# =============================================================================
# Override Detected Language
# =============================================================================

class TestOverrideDetectedLanguage:
    """PUT /api/translate/runs/{run_id}/records/{record_id}/languages/{language_code}/override-language"""

    def test_override_success(self):
        mock_db = MagicMock()
        mock_record = _mock_record()
        mock_lang = _mock_lang_entry()

        # First query returns record, second returns lang entry
        mock_db.query.return_value.filter.return_value.first.side_effect = [
            mock_record, mock_lang,
        ]

        from src.dependencies import get_db
        client = _make_client({get_db: lambda: mock_db})

        resp = client.put(
            "/api/translate/runs/run-1/records/rec-1/languages/es/override-language",
            json={"source_language": "fr"},
        )
        assert resp.status_code == 200
        data = resp.json()
        assert data["source_language_detected"] == "fr"
        assert data["language_overridden"] is True
        # Verify language_overridden was set
        assert mock_lang.language_overridden is True
        assert mock_lang.source_language_detected == "fr"

    def test_override_record_not_found_404(self):
        mock_db = MagicMock()
        mock_db.query.return_value.filter.return_value.first.side_effect = [
            None,  # record not found
        ]

        from src.dependencies import get_db
        client = _make_client({get_db: lambda: mock_db})

        resp = client.put(
            "/api/translate/runs/run-1/records/rec-missing/languages/es/override-language",
            json={"source_language": "fr"},
        )
        assert resp.status_code == 404
        assert "not found" in resp.text

    def test_override_lang_entry_not_found_404(self):
        mock_db = MagicMock()
        mock_record = _mock_record()
        mock_db.query.return_value.filter.return_value.first.side_effect = [
            mock_record,  # record found
            None,  # lang entry not found
        ]

        from src.dependencies import get_db
        client = _make_client({get_db: lambda: mock_db})

        resp = client.put(
            "/api/translate/runs/run-1/records/rec-1/languages/missing/override-language",
            json={"source_language": "fr"},
        )
        assert resp.status_code == 404
        assert "not found" in resp.text

    def test_override_generic_error_400(self):
        mock_db = MagicMock()
        mock_db.query.return_value.filter.side_effect = Exception("DB error")

        from src.dependencies import get_db
        client = _make_client({get_db: lambda: mock_db})

        resp = client.put(
            "/api/translate/runs/run-1/records/rec-1/languages/es/override-language",
            json={"source_language": "fr"},
        )
        assert resp.status_code == 400


# =============================================================================
# Inline Edit Translation
# =============================================================================

class TestInlineEditTranslation:
    """PUT /api/translate/runs/{run_id}/records/{record_id}/languages/{language_code}"""

    def test_inline_edit_success(self):
        with patch(
            "src.plugins.translate.service.InlineCorrectionService"
        ) as MockService:
            MockService.apply_inline_edit.return_value = {
                "status": "updated",
                "final_value": "corregido",
            }

            client = _make_client()
            resp = client.put(
                "/api/translate/runs/run-1/records/rec-1/languages/es",
                json={"final_value": "corregido"},
            )
        assert resp.status_code == 200
        data = resp.json()
        assert data["status"] == "updated"

    def test_inline_edit_with_dictionary_submit(self):
        with patch(
            "src.plugins.translate.service.InlineCorrectionService"
        ) as MockService:
            MockService.apply_inline_edit.return_value = {
                "status": "updated",
                "dictionary_entry_id": "entry-1",
            }

            client = _make_client()
            resp = client.put(
                "/api/translate/runs/run-1/records/rec-1/languages/es",
                json={
                    "final_value": "corregido",
                    "submit_to_dictionary": True,
                    "dictionary_id": "dict-1",
                },
            )
        assert resp.status_code == 200
        MockService.apply_inline_edit.assert_called_once()
        call_kwargs = MockService.apply_inline_edit.call_args.kwargs
        assert call_kwargs["submit_to_dictionary"] is True
        assert call_kwargs["dictionary_id"] == "dict-1"

    def test_inline_edit_value_error_400(self):
        with patch(
            "src.plugins.translate.service.InlineCorrectionService"
        ) as MockService:
            MockService.apply_inline_edit.side_effect = ValueError("Record not found")

            client = _make_client()
            resp = client.put(
                "/api/translate/runs/run-1/records/rec-missing/languages/es",
                json={"final_value": "corregido"},
            )
        assert resp.status_code == 400

    def test_inline_edit_generic_error_400(self):
        with patch(
            "src.plugins.translate.service.InlineCorrectionService"
        ) as MockService:
            MockService.apply_inline_edit.side_effect = RuntimeError("Unexpected")

            client = _make_client()
            resp = client.put(
                "/api/translate/runs/run-1/records/rec-1/languages/es",
                json={"final_value": "corregido"},
            )
        assert resp.status_code == 400

    def test_inline_edit_422_missing_final_value(self):
        client = _make_client()
        resp = client.put(
            "/api/translate/runs/run-1/records/rec-1/languages/es",
            json={},
        )
        assert resp.status_code == 422
        assert "final_value" in resp.text


# =============================================================================
# Bulk Find Replace
# =============================================================================

class TestBulkFindReplace:
    """POST /api/translate/runs/{run_id}/bulk-replace"""

    def test_bulk_preview_success(self):
        with patch(
            "src.plugins.translate.service.BulkFindReplaceService"
        ) as MockService:
            MockService.preview.return_value = [
                {"record_id": "rec-1", "current_value": "hola", "new_value": "hello"},
            ]

            client = _make_client()
            resp = client.post("/api/translate/runs/run-1/bulk-replace", json={
                "find_pattern": "hola",
                "replacement_text": "hello",
                "target_language": "es",
                "preview": True,
            })
        assert resp.status_code == 200
        data = resp.json()
        assert data["rows_affected"] == 1
        assert data["corrections_submitted"] == 0
        assert len(data["preview"]) == 1

    def test_bulk_apply_success(self):
        with patch(
            "src.plugins.translate.service.BulkFindReplaceService"
        ) as MockService:
            MockService.apply.return_value = {
                "rows_affected": 5,
                "corrections_submitted": 5,
                "preview": [],
            }

            client = _make_client()
            resp = client.post("/api/translate/runs/run-1/bulk-replace", json={
                "find_pattern": "hola",
                "replacement_text": "hello",
                "target_language": "es",
                "preview": False,
            })
        assert resp.status_code == 200
        data = resp.json()
        assert data["rows_affected"] == 5
        assert data["corrections_submitted"] == 5

    def test_bulk_replace_value_error_400(self):
        with patch(
            "src.plugins.translate.service.BulkFindReplaceService"
        ) as MockService:
            MockService.preview.side_effect = ValueError("Run not found")

            client = _make_client()
            resp = client.post("/api/translate/runs/run-1/bulk-replace", json={
                "find_pattern": "hola",
                "replacement_text": "hello",
                "target_language": "es",
                "preview": True,
            })
        assert resp.status_code == 400

    def test_bulk_replace_generic_error_400(self):
        with patch(
            "src.plugins.translate.service.BulkFindReplaceService"
        ) as MockService:
            MockService.apply.side_effect = RuntimeError("Bulk replace crashed")

            client = _make_client()
            resp = client.post("/api/translate/runs/run-1/bulk-replace", json={
                "find_pattern": "hola",
                "replacement_text": "hello",
                "target_language": "es",
                "preview": False,
            })
        assert resp.status_code == 400

    def test_bulk_replace_422_missing_fields(self):
        client = _make_client()
        resp = client.post("/api/translate/runs/run-1/bulk-replace", json={
            "target_language": "es",
        })
        assert resp.status_code == 422
        assert "find_pattern" in resp.text

    def test_bulk_replace_invalid_regex_422(self):
        """Pattern longer than 500 chars should be rejected."""
        client = _make_client()
        resp = client.post("/api/translate/runs/run-1/bulk-replace", json={
            "find_pattern": "x" * 501,
            "is_regex": True,
            "replacement_text": "y",
            "target_language": "es",
            "preview": True,
        })
        assert resp.status_code == 422
# #endregion Test.Api.TranslateRunEditRoutes
