# #region ResponseFieldCoverageTests [C:3] [TYPE Module] [SEMANTICS test, translate, field-coverage, serialization]
# @BRIEF Tests that verify full field coverage across the API response chain.
# @LAYER Test
# @RELATION BINDS_TO -> [job_to_response]
# @RELATION BINDS_TO -> [TranslateJobResponse]
# @RELATION BINDS_TO -> [duplicate_job]
# @RELATION BINDS_TO -> [get_run_status]
# @RELATION BINDS_TO -> [get_run_history]
# @INVARIANT Every field in TranslateJobResponse must be mapped in job_to_response.
# @INVARIANT Every data field in TranslationJob must be copied in duplicate_job.
# @INVARIANT Run dict serializers must include insert_method + connection_snapshot.
# @TEST_CONTRACT job_to_response maps ALL TranslateJobResponse fields
# @TEST_CONTRACT duplicate_job copies ALL TranslationJob data fields
# @TEST_CONTRACT get_run_status/get_run_history include insert_method
# @RATIONALE Regression guard: when new columns are added to models/schemas,
#   the response serializers and duplication logic must be updated too.
#   These tests fail if a field is silently defaulted.

from unittest.mock import MagicMock
import pytest

from src.schemas.translate import TranslateJobResponse
from src.plugins.translate.service_utils import job_to_response
from src.plugins.translate.orchestrator_query import get_run_history
from src.models.translate import TranslationJob, TranslationRun


# #region TestJobToResponse [C:2] [TYPE Function] [SEMANTICS test, field-coverage, serialization]
# @TEST_INVARIANT: job_to_response-maps-all-response-fields -> VERIFIED_BY: test_job_to_response_covers_all_fields
class TestJobToResponse:
    """Verify job_to_response() maps EVERY field from TranslateJobResponse."""

    # Fields set externally (not from job ORM)
    EXTERNAL_FIELDS = {"dictionary_ids"}

    def test_job_to_response_covers_all_fields(self):
        """Every field in TranslateJobResponse must be passed to the constructor."""
        import inspect
        from src.plugins.translate import service_utils

        # Get source of job_to_response
        src = inspect.getsource(service_utils.job_to_response)
        response_fields = set(TranslateJobResponse.model_fields.keys())
        needed = response_fields - self.EXTERNAL_FIELDS

        missing = []
        for field in sorted(needed):
            # Check the field name appears in the function body as a kwarg
            if f"{field}=" not in src:
                missing.append(field)

        assert not missing, (
            f"Fields present in TranslateJobResponse but NOT mapped in job_to_response(): {missing}. "
            f"Add them to service_utils.py job_to_response()."
        )
# #endregion TestJobToResponse


# #region TestDuplicateJob [C:2] [TYPE Function] [SEMANTICS test, field-coverage, duplication]
# @TEST_INVARIANT: duplicate_job-copies-all-fields -> VERIFIED_BY: test_duplicate_job_copies_all_fields
class TestDuplicateJob:
    """Verify duplicate_job() copies all data-bearing TranslationJob columns."""

    # Fields intentionally NOT copied
    SKIP_FIELDS = {
        "id",           # new UUID
        "status",       # set to "DRAFT" explicitly
        "created_by",   # set to current user
        "created_at",   # auto-set by ORM
        "updated_at",   # auto-set by ORM
        "name",         # set to f"{source.name} (Copy)"
    }

    def test_duplicate_job_copies_all_fields(self):
        """Every data-bearing ORM column should be referenced in duplicate_job()."""
        import inspect
        from src.plugins.translate import service

        src = inspect.getsource(service.TranslateJobService.duplicate_job)
        orm_cols = {c.name for c in TranslationJob.__table__.columns}
        data_cols = orm_cols - self.SKIP_FIELDS

        missing = []
        for col in sorted(data_cols):
            # Check the column name is assigned in duplicate_job body
            if f"{col}=" not in src and f"{col} =" not in src:
                missing.append(col)

        assert not missing, (
            f"ORM columns present but NOT copied in duplicate_job(): {missing}. "
            f"Add them to service.py duplicate_job()."
        )
# #endregion TestDuplicateJob


# #region TestRunSerializers [C:2] [TYPE Function] [SEMANTICS test, field-coverage, run, serialization]
# @TEST_INVARIANT: get_run_status-includes-insert_method -> VERIFIED_BY: test_get_run_status_includes_insert_method
# @TEST_INVARIANT: get_run_history-includes-insert_method -> VERIFIED_BY: test_get_run_history_includes_insert_method
class TestRunSerializers:
    """Verify run dict serializers include insert_method and connection_snapshot."""

    FIELDS = {"insert_method", "connection_snapshot"}

    def test_get_run_status_includes_insert_method(self):
        """get_run_status() dict must include insert_method and connection_snapshot."""
        import inspect
        from src.plugins.translate import orchestrator_aggregator

        src = inspect.getsource(orchestrator_aggregator.TranslationResultAggregator.get_run_status)
        for field in self.FIELDS:
            # The returned dict literal should contain the field
            if f'"{field}"' not in src and f"'{field}'" not in src:
                # Also check run. field access
                pass
            assert f'"{field}"' in src or f"'{field}'" in src or f"run.{field}" in src, (
                f"Field '{field}' missing from get_run_status() dict. "
                f"Add it to orchestrator_aggregator.py get_run_status()."
            )

    def test_get_run_history_includes_insert_method(self):
        """get_run_history() dict must include insert_method and connection_snapshot."""
        import inspect
        from src.plugins.translate import orchestrator_query

        src = inspect.getsource(orchestrator_query.get_run_history)
        for field in self.FIELDS:
            assert f'"{field}"' in src or f"'{field}'" in src or f"r.{field}" in src, (
                f"Field '{field}' missing from get_run_history() dict. "
                f"Add it to orchestrator_query.py get_run_history()."
            )
# #endregion TestRunSerializers


# #region TestRunModelColumns [C:1] [TYPE Function] [SEMANTICS test, field-coverage, orm]
# @TEST_EDGE: new column on TranslationRun without serialization -> test_catches
class TestRunModelColumns:
    """Verify that new columns on TranslationRun are not forgotten."""

    KNOWN_RUN_SERIALIZERS = {
        "orchestrator_query.get_run_history": [
            "id", "job_id", "status", "trigger_type",
            "started_at", "completed_at", "error_message",
            "total_records", "successful_records", "failed_records", "skipped_records",
            "cache_hits", "insert_status", "insert_method", "connection_snapshot",
            "superset_execution_id", "created_by", "created_at",
        ],
        "orchestrator_aggregator.get_run_status": [
            "id", "job_id", "status", "trigger_type",
            "started_at", "completed_at", "error_message",
            "total_records", "successful_records", "failed_records", "skipped_records",
            "cache_hits",             "insert_status", "insert_method", "connection_snapshot",
            "config_snapshot",
            "superset_execution_id", "batch_count", "language_stats",
            "event_invariants", "created_by", "created_at",
        ],
    }

    # ORM columns that are NOT expected in any run serializer
    SKIP_RUN_COLUMNS = {
        "key_hash", "config_hash", "dict_snapshot_hash",
        "id", "job_id",
        "records_inserted_at", "insert_batches_json",
        "superset_execution_log",
    }

    def test_all_run_columns_serialized(self):
        """Every TranslationRun column should appear in at least one serializer."""
        orm_cols = {c.name for c in TranslationRun.__table__.columns}
        expected_columns = orm_cols - self.SKIP_RUN_COLUMNS

        all_serialized = set()
        for fields in self.KNOWN_RUN_SERIALIZERS.values():
            all_serialized.update(fields)

        missing = expected_columns - all_serialized
        assert not missing, (
            f"TranslationRun columns not serialized in any run dict: {missing}. "
            f"Add them to get_run_status() and/or get_run_history()."
        )
# #endregion TestRunModelColumns
# #endregion ResponseFieldCoverageTests
