# #region TestRunExecutionService [C:3] [TYPE Module] [SEMANTICS test,translate,run,orchestration]
# @BRIEF Tests for RunExecutionService — filter_new_keys, load_preview_edits, key_hash, lang_stats.
# @RELATION BINDS_TO -> [RunExecutionService]
import uuid
from unittest.mock import MagicMock, patch

import pytest

from src.models.translate import (
    TranslationJob,
    TranslationRun,
    TranslationRecord,
    TranslationRunLanguageStats,
    TranslationPreviewSession,
    TranslationPreviewRecord,
    TranslationPreviewLanguage,
    TranslationLanguage,
)
from src.plugins.translate._run_service import RunExecutionService


# #region make_service [C:1] [TYPE Function]
def make_service(db=None) -> RunExecutionService:
    if db is None:
        db = MagicMock()
    return RunExecutionService(db, MagicMock(), "test_user")


def make_job(**overrides) -> TranslationJob:
    job = MagicMock(spec=TranslationJob)
    job.id = "job-1"
    job.target_key_cols = ["id"]
    job.source_key_cols = []
    for k, v in overrides.items():
        setattr(job, k, v)
    return job


def make_run(**overrides) -> TranslationRun:
    run = MagicMock(spec=TranslationRun)
    run.id = str(uuid.uuid4())
    run.job_id = "job-1"
    run.status = "RUNNING"
    for k, v in overrides.items():
        setattr(run, k, v)
    return run


def make_record(**overrides) -> TranslationRecord:
    rec = MagicMock(spec=TranslationRecord)
    rec.id = str(uuid.uuid4())
    rec.run_id = "run-1"
    rec.status = "SUCCESS"
    rec.source_data = {"id": 1, "name": "Alice"}
    rec.source_sql = "SELECT 1"
    rec.target_sql = "translated"
    for k, v in overrides.items():
        setattr(rec, k, v)
    return rec
# #endregion make_service


# #region TestFilterNewKeys [C:3] [TYPE Class]
class TestFilterNewKeys:
    """Verify _filter_new_keys correctly deduplicates by key columns."""

    def test_no_prev_run_returns_all(self):
        db = MagicMock()
        db.query.return_value.filter.return_value.filter.return_value.order_by.return_value.first.return_value = None

        svc = make_service(db)
        job = make_job(target_key_cols=["id"])
        rows = [{"source_data": {"id": 1}}, {"source_data": {"id": 2}}]

        result = svc._filter_new_keys(job, "run-1", rows)
        assert len(result) == 2

    def test_filters_duplicates(self):
        db = MagicMock()
        prev_run = make_run(status="COMPLETED", id="prev-run-1")

        def make_run_query():
            q = MagicMock()
            # Code calls: .filter(a, b, c).order_by(...).first()
            q.filter.return_value.order_by.return_value.first.return_value = prev_run
            return q

        def make_rec_query():
            q = MagicMock()
            rec = make_record(run_id="prev-run-1", source_data={"id": 1})
            # Code calls: .filter(a, b).all()
            q.filter.return_value.all.return_value = [rec]
            return q

        db.query.side_effect = lambda model: make_rec_query() if model == TranslationRecord else make_run_query()

        svc = make_service(db)
        job = make_job(target_key_cols=["id"])
        rows = [
            {"source_data": {"id": 1}},
            {"source_data": {"id": 2}},
            {"source_data": {"id": 3}},
        ]

        result = svc._filter_new_keys(job, "run-1", rows)
        assert len(result) == 2
        assert result[0]["source_data"]["id"] == 2

    def test_all_duplicates_returns_empty(self):
        db = MagicMock()
        prev_run = make_run(status="COMPLETED", id="prev-run-2")

        def make_run_query():
            q = MagicMock()
            q.filter.return_value.order_by.return_value.first.return_value = prev_run
            return q

        def make_rec_query():
            q = MagicMock()
            rec1 = make_record(run_id="prev-run-2", source_data={"id": 1})
            rec2 = make_record(run_id="prev-run-2", source_data={"id": 2})
            q.filter.return_value.all.return_value = [rec1, rec2]
            return q

        db.query.side_effect = lambda model: make_rec_query() if model == TranslationRecord else make_run_query()

        svc = make_service(db)
        job = make_job(target_key_cols=["id"])
        rows = [
            {"source_data": {"id": 1}},
            {"source_data": {"id": 2}},
        ]

        result = svc._filter_new_keys(job, "run-1", rows)
        assert len(result) == 0

    def test_no_key_cols_returns_all(self):
        db = MagicMock()
        svc = make_service(db)
        job = make_job(target_key_cols=[], source_key_cols=[])
        rows = [{"source_data": {"id": 1}}]

        result = svc._filter_new_keys(job, "run-1", rows)
        assert len(result) == 1

    def test_missing_source_data_handles_gracefully(self):
        db = MagicMock()
        db.query.return_value.filter.return_value.filter.return_value.order_by.return_value.first.return_value = None

        svc = make_service(db)
        job = make_job(target_key_cols=["id"])
        rows = [{"source_data": None}, {}]

        result = svc._filter_new_keys(job, "run-1", rows)
        assert len(result) == 2
# #endregion TestFilterNewKeys


# #region TestLoadPreviewEdits [C:3] [TYPE Class]
class TestLoadPreviewEdits:
    """Verify _load_preview_edits loads approved/edited preview translations."""

    def test_no_session_returns_empty_cache(self):
        db = MagicMock()
        db.query.return_value.filter.return_value.filter.return_value.order_by.return_value.first.return_value = None

        svc = make_service(db)
        svc._load_preview_edits("job-1")
        assert svc._preview_edits_cache == {}

    def test_loads_edited_languages(self):
        db = MagicMock()

        def query_side_effect(model):
            q = MagicMock()
            if model == TranslationPreviewSession:
                session = MagicMock(spec=TranslationPreviewSession)
                session.id = "preview-1"
                # Code calls: .filter(a, b).order_by(...).first()
                q.filter.return_value.order_by.return_value.first.return_value = session
            elif model == TranslationPreviewRecord:
                lang_entry = MagicMock()
                lang_entry.language_code = "ru"
                lang_entry.status = "edited"
                lang_entry.user_edit = "исправленный"
                lang_entry.final_value = "исправленный"

                record = MagicMock()
                record.source_data = {"id": 1}
                record.languages = [lang_entry]
                # Code calls: .filter(a).all()
                q.filter.return_value.all.return_value = [record]
            return q

        db.query.side_effect = query_side_effect

        svc = make_service(db)
        svc._load_preview_edits("job-1")
        assert svc._preview_edits_cache is not None
        assert len(svc._preview_edits_cache) == 1

    def test_approved_language_loaded(self):
        db = MagicMock()

        def query_side_effect(model):
            q = MagicMock()
            if model == TranslationPreviewSession:
                session = MagicMock(spec=TranslationPreviewSession)
                session.id = "preview-1"
                q.filter.return_value.order_by.return_value.first.return_value = session
            elif model == TranslationPreviewRecord:
                lang_entry = MagicMock()
                lang_entry.language_code = "de"
                lang_entry.status = "approved"
                lang_entry.user_edit = "Guten Tag"
                lang_entry.final_value = "Guten Tag"

                record = MagicMock()
                record.source_data = {"id": 2}
                record.languages = [lang_entry]
                q.filter.return_value.all.return_value = [record]
            return q

        db.query.side_effect = query_side_effect

        svc = make_service(db)
        svc._load_preview_edits("job-1")
        assert len(svc._preview_edits_cache) == 1
# #endregion TestLoadPreviewEdits


# #region TestKeyHash [C:3] [TYPE Class]
class TestKeyHash:
    """Verify _compute_key_hash produces consistent hashes."""

    def test_hash_consistent(self):
        svc = make_service()
        h1 = svc._compute_key_hash({"id": 1, "name": "Alice"})
        h2 = svc._compute_key_hash({"name": "Alice", "id": 1})
        assert h1 == h2  # same data, sorted keys

    def test_hash_different_data(self):
        svc = make_service()
        h1 = svc._compute_key_hash({"id": 1})
        h2 = svc._compute_key_hash({"id": 2})
        assert h1 != h2

    def test_hash_empty_dict(self):
        svc = make_service()
        h = svc._compute_key_hash({})
        assert isinstance(h, str)
        assert len(h) == 16
# #endregion TestKeyHash


# #region TestUpdateLangStatsIncremental [C:3] [TYPE Class]
class TestUpdateLangStatsIncremental:
    """Verify _update_language_stats_incremental aggregates correctly."""

    def test_updates_stats(self):
        db = MagicMock()
        record = make_record()

        def query_side_effect(model):
            q = MagicMock()
            if model == TranslationRecord:
                # Code calls: .filter(run_id == ...).all()
                q.filter.return_value.all.return_value = [record]
            elif model == TranslationLanguage:
                lang = MagicMock()
                lang.language_code = "ru"
                lang.status = "translated"
                lang.translated_value = "Привет"
                # Code calls: .filter(record_id.in_(...)).all()
                q.filter.return_value.all.return_value = [lang]
            return q

        db.query.side_effect = query_side_effect

        svc = make_service(db)
        ru_stat = MagicMock(spec=TranslationRunLanguageStats)
        svc._update_language_stats_incremental("run-1", {"ru": ru_stat})

        assert ru_stat.total_rows == 1
        assert ru_stat.translated_rows == 1

    def test_no_records_returns_early(self):
        db = MagicMock()
        db.query.return_value.filter.return_value.all.return_value = []

        svc = make_service(db)
        svc._update_language_stats_incremental("run-1", {"ru": MagicMock()})
        # Should not query language entries when there are no records
# #endregion TestUpdateLangStatsIncremental
# #endregion TestRunExecutionService
