# #region TestBatchInsert [C:3] [TYPE Module] [SEMANTICS test,translate,batch,insert]
# @BRIEF Tests for _batch_insert.py — column building, row building, context keys.
# @RELATION BINDS_TO -> [_batch_insert_module]
import uuid
from unittest.mock import AsyncMock, MagicMock, patch

import pytest

from src.models.translate import TranslationJob, TranslationLanguage, TranslationRecord
from src.plugins.translate._batch_insert import (
    _build_target_columns,
    _build_context_keys,
    _build_insert_rows,
    _fetch_batch_records,
    _generate_insert_sql,
)


# #region make_job [C:1] [TYPE Function]
def make_job(**overrides) -> TranslationJob:
    job = MagicMock(spec=TranslationJob)
    job.target_key_cols = ["id"]
    job.target_column = "translated_text"
    job.translation_column = "source_text"
    job.target_language_column = "lang_code"
    job.target_source_column = "source_text"
    job.target_source_language_column = "src_lang"
    job.target_schema = "public"
    job.target_table = "translations"
    job.target_languages = ["ru"]
    job.context_columns = []
    job.target_dialect = "clickhouse"
    job.database_dialect = None
    job.upsert_strategy = "INSERT"
    job.environment_id = "env-1"
    job.source_dialect = "postgresql"
    for k, v in overrides.items():
        setattr(job, k, v)
    return job


def make_record(**overrides) -> TranslationRecord:
    rec = MagicMock(spec=TranslationRecord)
    rec.id = str(uuid.uuid4())
    rec.source_sql = "Hello"
    rec.target_sql = "Привет"
    rec.source_data = {"id": 1, "extra": "data"}
    rec.status = "SUCCESS"
    rec.languages = []
    for k, v in overrides.items():
        setattr(rec, k, v)
    return rec


def make_lang(code: str = "ru", value: str = "Привет", detected: str = "en", status: str = "completed") -> MagicMock:
    lang = MagicMock(spec=TranslationLanguage)
    lang.language_code = code
    lang.translated_value = value
    lang.final_value = value
    lang.source_language_detected = detected
    lang.status = status
    return lang
# #endregion make_job


# #region TestBuildColumns [C:2] [TYPE Class]
class TestBuildColumns:
    """Verify _build_target_columns assembles correct column list."""

    def test_basic_columns(self):
        job = make_job()
        cols = _build_target_columns(job, "translated_text")
        assert "translated_text" in cols
        assert "lang_code" in cols
        assert "source_text" in cols
        assert "src_lang" in cols
        assert "context" in cols
        assert "is_original" in cols

    def test_no_key_cols(self):
        job = make_job(target_key_cols=[])
        cols = _build_target_columns(job, "translated_text")
        assert "id" not in cols

    def test_dedup_same_column(self):
        job = make_job(target_key_cols=["source_text"])
        cols = _build_target_columns(job, "translated_text")
        # source_text appears in key_cols and in target_source_column — should be deduped
        assert cols.count("source_text") == 1

    def test_no_effective_target(self):
        job = make_job(target_column=None, translation_column=None)
        cols = _build_target_columns(job, None)
        assert all(c in cols for c in ["context", "is_original"])
# #endregion TestBuildColumns


# #region TestBuildContextKeys [C:2] [TYPE Class]
class TestBuildContextKeys:
    """Verify _build_context_keys builds correct context list."""

    def test_empty_context(self):
        job = make_job(context_columns=[])
        keys = _build_context_keys(job, "translated_text")
        assert "source_text" in keys  # translation_column != effective_target

    def test_with_context_columns(self):
        job = make_job(context_columns=["category", "author"])
        keys = _build_context_keys(job, "translated_text")
        assert "category" in keys
        assert "author" in keys

    def test_no_extra_when_same(self):
        job = make_job(translation_column="translated_text")
        keys = _build_context_keys(job, "translated_text")
        assert keys == []
# #endregion TestBuildContextKeys


# #region TestBuildInsertRows [C:2] [TYPE Class]
class TestBuildInsertRows:
    """Verify _build_insert_rows produces correct row dicts."""

    def test_single_record_no_languages(self):
        job = make_job(target_key_cols=["id"])
        rec = make_record()
        rows = _build_insert_rows([rec], job, "translated_text", "ru", [])

        # Should produce 2 rows: original + fallback
        assert len(rows) == 2
        assert rows[0]["is_original"] == 1
        assert rows[1]["is_original"] == 0
        assert rows[1]["translated_text"] == rec.target_sql

    def test_single_record_with_languages(self):
        job = make_job(target_key_cols=["id"])
        rec = make_record(languages=[make_lang("ru", "Привет мир")])
        rows = _build_insert_rows([rec], job, "translated_text", "ru", [])

        # 2 rows: original + translated
        assert len(rows) == 2
        assert rows[1]["translated_text"] == "Привет мир"
        assert rows[1]["lang_code"] == "ru"

    def test_skip_matching_language(self):
        """Skip language code that matches detected source language."""
        job = make_job(target_key_cols=["id"])
        rec = make_record(languages=[make_lang("en", "Hello", detected="en")])
        rows = _build_insert_rows([rec], job, "translated_text", "en", [])

        # Only the original row — language is skipped because en == detected
        assert len(rows) == 1

    def test_source_column_values(self):
        job = make_job(target_key_cols=["id"])
        rec = make_record()
        rows = _build_insert_rows([rec], job, "translated_text", "ru", [])

        assert rows[0]["source_text"] == "Hello"
        assert rows[0]["src_lang"] == "und"  # no languages on record

    def test_context_data(self):
        job = make_job(target_key_cols=["id"], context_columns=["extra"])
        rec = make_record()
        rows = _build_insert_rows([rec], job, "translated_text", "ru", ["extra"])

        import json
        context = json.loads(rows[0]["context"])
        assert context.get("extra") == "data"
# #endregion TestBuildInsertRows


# #region TestFetchBatchRecords [C:2] [TYPE Class]
class TestFetchBatchRecords:
    """Verify _fetch_batch_records queries correctly."""

    def test_fetch_returns_records(self):
        db = MagicMock()
        db.query.return_value.options.return_value.filter.return_value.all.return_value = [make_record()]

        result = _fetch_batch_records(db, "batch-1")
        assert len(result) == 1

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

        result = _fetch_batch_records(db, "batch-1")
        assert result == []
# #endregion TestFetchBatchRecords


# #region TestGenerateInsertSQL [C:2] [TYPE Class]
class TestGenerateInsertSQL:
    """Verify _generate_insert_sql produces SQL."""

    def test_generates_sql(self):
        job = make_job(target_schema="public", target_table="test_table", upsert_strategy="INSERT")
        sql, count = _generate_insert_sql(
            dialect="clickhouse",
            job=job,
            columns=["id", "translated_text"],
            rows_for_sql=[{"id": 1, "translated_text": "hello"}],
        )
        assert sql is not None
        assert "INSERT INTO" in sql
        assert count == 1

    def test_generates_upsert(self):
        job = make_job(target_schema="public", target_table="test_table", upsert_strategy="MERGE", target_key_cols=["id"])
        sql, count = _generate_insert_sql(
            dialect="clickhouse",
            job=job,
            columns=["id", "translated_text"],
            rows_for_sql=[{"id": 1, "translated_text": "hello"}],
        )
        assert sql is not None
        assert count == 1

    def test_unknown_dialect_falls_back(self):
        job = make_job(target_schema="public", target_table="test")
        sql, count = _generate_insert_sql(
            dialect="unknown_dialect_xyz",
            job=job,
            columns=["col1"],
            rows_for_sql=[{"col1": "val"}],
        )
        # Falls back to default quoting
        assert sql is not None
        assert count == 1
# #endregion TestGenerateInsertSQL
# #endregion TestBatchInsert
