# #region Test.Api.TranslateDictionaryRoutes [C:3] [TYPE Module] [SEMANTICS test,translate,dictionary,api,crud]
# @BRIEF Tests for _dictionary_routes.py — terminology dictionary CRUD, entries, and import.
# @RELATION BINDS_TO -> [TranslateDictionaryRoutesModule]
# @TEST_EDGE: dictionary_not_found -> 404
# @TEST_EDGE: dictionary_conflict -> 409 (blocked by active jobs)
# @TEST_EDGE: entry_already_exists -> 409
# @TEST_EDGE: entry_not_found -> 404
# @TEST_EDGE: import_invalid_on_conflict -> 422
# @TEST_EDGE: import_value_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._dictionary_routes import router
    from src.dependencies import 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_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)


# Shared mock dictionary ORM row
def _mock_dict(
    id="dict-1", name="Test Dict", description="A test dictionary",
    is_active=True, created_by="admin",
    created_at=None, updated_at=None,
):
    d = MagicMock()
    d.id = id
    d.name = name
    d.description = description
    d.is_active = is_active
    d.created_by = created_by
    d.created_at = created_at or __import__("datetime").datetime(2026, 1, 1)
    d.updated_at = updated_at
    return d


def _mock_entry(
    id="entry-1", dictionary_id="dict-1",
    source_term="hello", source_term_normalized="hello",
    target_term="hola",
    source_language="en", target_language="es",
    context_notes=None, context_data=None,
    usage_notes=None, has_context=False,
    is_regex=False, context_source=None,
    origin_source_language=None,
    created_at=None, updated_at=None,
):
    e = MagicMock()
    e.id = id
    e.dictionary_id = dictionary_id
    e.source_term = source_term
    e.source_term_normalized = source_term_normalized
    e.target_term = target_term
    e.source_language = source_language
    e.target_language = target_language
    e.context_notes = context_notes
    e.context_data = context_data
    e.usage_notes = usage_notes
    e.has_context = has_context
    e.is_regex = is_regex
    e.context_source = context_source
    e.origin_source_language = origin_source_language
    e.created_at = created_at or __import__("datetime").datetime(2026, 1, 1)
    e.updated_at = updated_at
    return e


# =============================================================================
# Dictionaries CRUD
# =============================================================================

class TestListDictionaries:
    """GET /api/translate/dictionaries"""

    def test_list_success(self):
        mock_mgr = MagicMock()
        mock_mgr.list_dictionaries.return_value = ([_mock_dict()], 1)

        with patch("src.api.routes.translate._dictionary_routes.DictionaryManager", mock_mgr):
            client = _make_client()
            resp = client.get("/api/translate/dictionaries")
        assert resp.status_code == 200
        data = resp.json()
        assert data["total"] == 1
        assert len(data["items"]) == 1
        assert data["items"][0]["id"] == "dict-1"
        assert data["items"][0]["name"] == "Test Dict"

    def test_list_empty(self):
        mock_mgr = MagicMock()
        mock_mgr.list_dictionaries.return_value = ([], 0)

        with patch("src.api.routes.translate._dictionary_routes.DictionaryManager", mock_mgr):
            client = _make_client()
            resp = client.get("/api/translate/dictionaries")
        assert resp.status_code == 200
        data = resp.json()
        assert data["total"] == 0
        assert data["items"] == []

    def test_list_pagination_params(self):
        mock_mgr = MagicMock()
        mock_mgr.list_dictionaries.return_value = ([_mock_dict()], 1)

        with patch("src.api.routes.translate._dictionary_routes.DictionaryManager", mock_mgr):
            client = _make_client()
            resp = client.get("/api/translate/dictionaries?page=2&page_size=10")
        assert resp.status_code == 200
        data = resp.json()
        assert data["page"] == 2
        assert data["page_size"] == 10


class TestGetDictionary:
    """GET /api/translate/dictionaries/{dictionary_id}"""

    def test_get_success(self):
        mock_dict = _mock_dict()
        mock_mgr = MagicMock()
        mock_mgr.get_dictionary.return_value = mock_dict

        mock_db = MagicMock()
        mock_db.query.return_value.filter.return_value.count.return_value = 5

        with patch("src.api.routes.translate._dictionary_routes.DictionaryManager", mock_mgr):
            from src.dependencies import get_db
            client = _make_client({get_db: lambda: mock_db})
            resp = client.get("/api/translate/dictionaries/dict-1")
        assert resp.status_code == 200
        data = resp.json()
        assert data["id"] == "dict-1"
        assert data["entry_count"] == 5

    def test_get_not_found_404(self):
        mock_mgr = MagicMock()
        mock_mgr.get_dictionary.side_effect = ValueError("Dictionary not found")

        with patch("src.api.routes.translate._dictionary_routes.DictionaryManager", mock_mgr):
            client = _make_client()
            resp = client.get("/api/translate/dictionaries/dict-missing")
        assert resp.status_code == 404
        assert "Dictionary not found" in resp.text


class TestCreateDictionary:
    """POST /api/translate/dictionaries"""

    def test_create_success(self):
        mock_dict = _mock_dict(id="dict-new", name="New Dict")
        mock_mgr = MagicMock()
        mock_mgr.create_dictionary.return_value = mock_dict

        with patch("src.api.routes.translate._dictionary_routes.DictionaryManager", mock_mgr):
            client = _make_client()
            resp = client.post("/api/translate/dictionaries", json={
                "name": "New Dict",
                "description": "A new dictionary",
                "is_active": True,
            })
        assert resp.status_code == 201
        data = resp.json()
        assert data["id"] == "dict-new"
        assert data["entry_count"] == 0

    def test_create_missing_name_422(self):
        client = _make_client()
        resp = client.post("/api/translate/dictionaries", json={
            "description": "Missing name",
        })
        assert resp.status_code == 422
        assert "name" in resp.text


class TestUpdateDictionary:
    """PUT /api/translate/dictionaries/{dictionary_id}"""

    def test_update_success(self):
        mock_dict = _mock_dict(id="dict-1", name="Updated Dict")
        mock_mgr = MagicMock()
        mock_mgr.update_dictionary.return_value = mock_dict

        mock_db = MagicMock()
        mock_db.query.return_value.filter.return_value.count.return_value = 3

        with patch("src.api.routes.translate._dictionary_routes.DictionaryManager", mock_mgr):
            from src.dependencies import get_db
            client = _make_client({get_db: lambda: mock_db})
            resp = client.put("/api/translate/dictionaries/dict-1", json={
                "name": "Updated Dict",
                "is_active": False,
            })
        assert resp.status_code == 200
        data = resp.json()
        assert data["name"] == "Updated Dict"
        assert data["entry_count"] == 3

    def test_update_not_found_404(self):
        mock_mgr = MagicMock()
        mock_mgr.update_dictionary.side_effect = ValueError("Dictionary not found")

        with patch("src.api.routes.translate._dictionary_routes.DictionaryManager", mock_mgr):
            client = _make_client()
            resp = client.put("/api/translate/dictionaries/dict-missing", json={
                "name": "Nope",
                "is_active": True,
            })
        assert resp.status_code == 404


class TestDeleteDictionary:
    """DELETE /api/translate/dictionaries/{dictionary_id}"""

    def test_delete_success(self):
        mock_mgr = MagicMock()
        mock_mgr.delete_dictionary.return_value = None

        with patch("src.api.routes.translate._dictionary_routes.DictionaryManager", mock_mgr):
            client = _make_client()
            resp = client.delete("/api/translate/dictionaries/dict-1")
        assert resp.status_code == 204

    def test_delete_not_found_404(self):
        mock_mgr = MagicMock()
        mock_mgr.delete_dictionary.side_effect = ValueError("Dictionary not found")

        with patch("src.api.routes.translate._dictionary_routes.DictionaryManager", mock_mgr):
            client = _make_client()
            resp = client.delete("/api/translate/dictionaries/dict-missing")
        assert resp.status_code == 404

    def test_delete_conflict_409(self):
        mock_mgr = MagicMock()
        mock_mgr.delete_dictionary.side_effect = ValueError(
            "Dictionary has active/scheduled jobs attached"
        )

        with patch("src.api.routes.translate._dictionary_routes.DictionaryManager", mock_mgr):
            client = _make_client()
            resp = client.delete("/api/translate/dictionaries/dict-busy")
        assert resp.status_code == 409
        assert "active/scheduled" in resp.text


# =============================================================================
# Dictionary Entries
# =============================================================================

class TestListDictionaryEntries:
    """GET /api/translate/dictionaries/{dictionary_id}/entries"""

    def test_list_entries_success(self):
        entry = _mock_entry(id="e1")
        mock_mgr = MagicMock()
        mock_mgr.list_entries.return_value = ([entry], 1)

        with patch("src.api.routes.translate._dictionary_routes.DictionaryManager", mock_mgr):
            client = _make_client()
            resp = client.get("/api/translate/dictionaries/dict-1/entries")
        assert resp.status_code == 200
        data = resp.json()
        assert data["total"] == 1
        assert data["items"][0]["id"] == "e1"
        assert data["items"][0]["source_term"] == "hello"

    def test_list_entries_with_language_filters(self):
        entry_en_es = _mock_entry(id="e1", source_language="en", target_language="es")
        entry_fr_de = _mock_entry(id="e2", source_language="fr", target_language="de")
        mock_mgr = MagicMock()
        mock_mgr.list_entries.return_value = ([entry_en_es, entry_fr_de], 2)

        with patch("src.api.routes.translate._dictionary_routes.DictionaryManager", mock_mgr):
            client = _make_client()
            resp = client.get(
                "/api/translate/dictionaries/dict-1/entries"
                "?source_language=en&target_language=es"
            )
        assert resp.status_code == 200
        data = resp.json()
        # Only en→es passes filter
        assert data["total"] == 1
        assert data["items"][0]["id"] == "e1"

    def test_list_entries_only_target_language_filter(self):
        """Filter by target_language only — covers source skip at line 193 then target skip at line 195."""
        entry_en_es = _mock_entry(id="e1", source_language="en", target_language="es")
        entry_fr_de = _mock_entry(id="e2", source_language="fr", target_language="de")
        mock_mgr = MagicMock()
        mock_mgr.list_entries.return_value = ([entry_en_es, entry_fr_de], 2)

        with patch("src.api.routes.translate._dictionary_routes.DictionaryManager", mock_mgr):
            client = _make_client()
            resp = client.get(
                "/api/translate/dictionaries/dict-1/entries?target_language=de"
            )
        assert resp.status_code == 200
        data = resp.json()
        # Only fr→de passes target_language filter
        assert data["total"] == 1
        assert data["items"][0]["id"] == "e2"

    def test_list_entries_not_found_404(self):
        mock_mgr = MagicMock()
        mock_mgr.list_entries.side_effect = ValueError("Dictionary not found")

        with patch("src.api.routes.translate._dictionary_routes.DictionaryManager", mock_mgr):
            client = _make_client()
            resp = client.get("/api/translate/dictionaries/dict-missing/entries")
        assert resp.status_code == 404


class TestAddDictionaryEntry:
    """POST /api/translate/dictionaries/{dictionary_id}/entries"""

    def test_add_entry_success(self):
        entry = _mock_entry(id="e-new", dictionary_id="dict-1")
        mock_mgr = MagicMock()
        mock_mgr.add_entry.return_value = entry

        with patch("src.api.routes.translate._dictionary_routes.DictionaryManager", mock_mgr):
            client = _make_client()
            resp = client.post("/api/translate/dictionaries/dict-1/entries", json={
                "source_term": "hello",
                "target_term": "hola",
            })
        assert resp.status_code == 201
        data = resp.json()
        assert data["id"] == "e-new"
        assert data["source_term"] == "hello"
        assert data["target_term"] == "hola"

    def test_add_entry_conflict_409(self):
        mock_mgr = MagicMock()
        mock_mgr.add_entry.side_effect = ValueError("Entry already exists")

        with patch("src.api.routes.translate._dictionary_routes.DictionaryManager", mock_mgr):
            client = _make_client()
            resp = client.post("/api/translate/dictionaries/dict-1/entries", json={
                "source_term": "hello",
                "target_term": "hola",
            })
        assert resp.status_code == 409
        assert "already exists" in resp.text

    def test_add_entry_not_found_404(self):
        mock_mgr = MagicMock()
        mock_mgr.add_entry.side_effect = ValueError("not found")

        with patch("src.api.routes.translate._dictionary_routes.DictionaryManager", mock_mgr):
            client = _make_client()
            resp = client.post("/api/translate/dictionaries/dict-missing/entries", json={
                "source_term": "hello",
                "target_term": "hola",
            })
        assert resp.status_code == 404

    def test_add_entry_422_missing_source(self):
        client = _make_client()
        resp = client.post("/api/translate/dictionaries/dict-1/entries", json={
            "target_term": "hola",
        })
        assert resp.status_code == 422


class TestEditDictionaryEntry:
    """PUT /api/translate/dictionaries/{dictionary_id}/entries/{entry_id}"""

    def test_edit_entry_success(self):
        entry = _mock_entry(id="e1", target_term="updated_hola")
        mock_mgr = MagicMock()
        mock_mgr.edit_entry.return_value = entry

        with patch("src.api.routes.translate._dictionary_routes.DictionaryManager", mock_mgr):
            client = _make_client()
            resp = client.put(
                "/api/translate/dictionaries/dict-1/entries/e1", json={
                    "source_term": "hello",
                    "target_term": "updated_hola",
                }
            )
        assert resp.status_code == 200
        assert resp.json()["target_term"] == "updated_hola"

    def test_edit_entry_conflict_409(self):
        mock_mgr = MagicMock()
        mock_mgr.edit_entry.side_effect = ValueError("Entry already exists")

        with patch("src.api.routes.translate._dictionary_routes.DictionaryManager", mock_mgr):
            client = _make_client()
            resp = client.put(
                "/api/translate/dictionaries/dict-1/entries/e1", json={
                    "source_term": "hello",
                    "target_term": "hola",
                }
            )
        assert resp.status_code == 409

    def test_edit_entry_not_found_404(self):
        mock_mgr = MagicMock()
        mock_mgr.edit_entry.side_effect = ValueError("Entry not found")

        with patch("src.api.routes.translate._dictionary_routes.DictionaryManager", mock_mgr):
            client = _make_client()
            resp = client.put(
                "/api/translate/dictionaries/dict-1/entries/e-missing", json={
                    "source_term": "hello",
                    "target_term": "hola",
                }
            )
        assert resp.status_code == 404


class TestDeleteDictionaryEntry:
    """DELETE /api/translate/dictionaries/{dictionary_id}/entries/{entry_id}"""

    def test_delete_entry_success(self):
        mock_mgr = MagicMock()
        mock_mgr.delete_entry.return_value = None

        with patch("src.api.routes.translate._dictionary_routes.DictionaryManager", mock_mgr):
            client = _make_client()
            resp = client.delete("/api/translate/dictionaries/dict-1/entries/e1")
        assert resp.status_code == 204

    def test_delete_entry_not_found_404(self):
        mock_mgr = MagicMock()
        mock_mgr.delete_entry.side_effect = ValueError("Entry not found")

        with patch("src.api.routes.translate._dictionary_routes.DictionaryManager", mock_mgr):
            client = _make_client()
            resp = client.delete("/api/translate/dictionaries/dict-1/entries/e-missing")
        assert resp.status_code == 404


# =============================================================================
# Import
# =============================================================================

class TestImportDictionaryEntries:
    """POST /api/translate/dictionaries/{dictionary_id}/import"""

    def test_import_success(self):
        mock_mgr = MagicMock()
        mock_mgr.import_entries.return_value = {
            "total": 2, "created": 2, "updated": 0, "skipped": 0, "errors": [],
        }

        with patch("src.api.routes.translate._dictionary_routes.DictionaryManager", mock_mgr):
            client = _make_client()
            resp = client.post("/api/translate/dictionaries/dict-1/import", json={
                "content": "source,target\nhello,hola\nbye,adiós",
                "on_conflict": "overwrite",
            })
        assert resp.status_code == 200
        data = resp.json()
        assert data["total"] == 2
        assert data["created"] == 2

    def test_import_invalid_on_conflict_422(self):
        client = _make_client()
        resp = client.post("/api/translate/dictionaries/dict-1/import", json={
            "content": "source,target\nhello,hola",
            "on_conflict": "invalid_option",
        })
        assert resp.status_code == 422
        assert "on_conflict" in resp.text

    def test_import_value_error_400(self):
        mock_mgr = MagicMock()
        mock_mgr.import_entries.side_effect = ValueError("Invalid CSV format")

        with patch("src.api.routes.translate._dictionary_routes.DictionaryManager", mock_mgr):
            client = _make_client()
            resp = client.post("/api/translate/dictionaries/dict-1/import", json={
                "content": "broken content",
                "on_conflict": "overwrite",
            })
        assert resp.status_code == 400
        assert "Invalid CSV format" in resp.text

    def test_import_preview_only(self):
        mock_mgr = MagicMock()
        mock_mgr.import_entries.return_value = {
            "total": 1, "created": 0, "updated": 0, "skipped": 0,
            "errors": [], "preview": [{"source_term": "hello"}],
        }

        with patch("src.api.routes.translate._dictionary_routes.DictionaryManager", mock_mgr):
            client = _make_client()
            resp = client.post("/api/translate/dictionaries/dict-1/import", json={
                "content": "source,target\nhello,hola",
                "on_conflict": "keep_existing",
                "preview_only": True,
            })
        assert resp.status_code == 200
        data = resp.json()
        assert "preview" in data
        assert data["preview"][0]["source_term"] == "hello"
# #endregion Test.Api.TranslateDictionaryRoutes
