# #region Test.OrchestratorAggregator [C:3] [TYPE Module] [SEMANTICS test, translate, aggregator, records, history]
# @BRIEF Tests for orchestrator_aggregator.py — TranslationResultAggregator.
# @RELATION BINDS_TO -> [TranslationResultAggregator]
# @TEST_CONTRACT: get_run_status -> dict | run status with statistics
# @TEST_CONTRACT: get_run_records -> dict | delegated to orchestrator_query
# @TEST_CONTRACT: get_run_history -> tuple[int, list] | delegated to orchestrator_query
# @TEST_CONTRACT: update_language_stats -> None | delegated to orchestrator_lang_stats
# @TEST_EDGE: missing_run
# @TEST_EDGE: empty_language_stats

import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent / "src"))

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

from datetime import datetime, timezone
from unittest.mock import MagicMock, patch

import pytest

from src.plugins.translate.orchestrator_aggregator import TranslationResultAggregator


class TestGetRunStatus:
    """TranslationResultAggregator.get_run_status — run status with stats."""

    def _make_run(self, **kwargs):
        run = MagicMock()
        run.id = "run-1"
        run.job_id = "job-1"
        run.status = "COMPLETED"
        run.trigger_type = "manual"
        run.started_at = datetime(2026, 1, 15, 12, 0, 0, tzinfo=timezone.utc)
        run.completed_at = datetime(2026, 1, 15, 13, 0, 0, tzinfo=timezone.utc)
        run.error_message = None
        run.total_records = 100
        run.successful_records = 95
        run.failed_records = 3
        run.skipped_records = 2
        run.cache_hits = 5
        run.insert_status = "success"
        run.insert_method = "cte_insert"
        run.connection_snapshot = {"db": "test"}
        run.config_snapshot = {"batch_size": 50}
        run.superset_execution_id = "q-1"
        run.created_by = "user"
        run.created_at = datetime(2026, 1, 15, 12, 0, 0, tzinfo=timezone.utc)
        for k, v in kwargs.items():
            setattr(run, k, v)
        return run

    def _make_lang_stat(self, code: str, **kwargs):
        ls = MagicMock()
        ls.language_code = code
        ls.total_rows = 50
        ls.translated_rows = 48
        ls.failed_rows = 1
        ls.skipped_rows = 1
        ls.token_count = 1000
        ls.estimated_cost = 0.002
        for k, v in kwargs.items():
            setattr(ls, k, v)
        return ls

    def test_valid_run_returns_full_status(self):
        """Valid run returns dict with all fields."""
        db = MagicMock()
        event_log = MagicMock()
        run = self._make_run()
        db.query.return_value.filter.return_value.first.return_value = run

        db.query.return_value.filter.return_value.count.return_value = 5  # batch_count

        event_log.get_run_event_summary.return_value = {
            "has_run_started": True,
            "terminal_event_count": 1,
            "invariant_valid": True,
        }

        lang_stats = [self._make_lang_stat("ru"), self._make_lang_stat("de")]
        db.query.return_value.filter.return_value.all.return_value = lang_stats

        aggregator = TranslationResultAggregator(db, event_log)
        result = aggregator.get_run_status("run-1")

        assert result["id"] == "run-1"
        assert result["status"] == "COMPLETED"
        assert result["total_records"] == 100
        assert result["batch_count"] == 5
        assert len(result["language_stats"]) == 2
        assert result["language_stats"][0]["language_code"] == "ru"
        assert result["language_stats"][0]["total_rows"] == 50
        assert result["event_invariants"]["invariant_valid"] is True

    def test_missing_run_raises(self):
        """Missing run raises ValueError."""
        db = MagicMock()
        event_log = MagicMock()
        db.query.return_value.filter.return_value.first.return_value = None

        aggregator = TranslationResultAggregator(db, event_log)
        with pytest.raises(ValueError, match="Run 'bad-id' not found"):
            aggregator.get_run_status("bad-id")

    def test_no_language_stats_returns_empty(self):
        """No language stats returns empty list."""
        db = MagicMock()
        event_log = MagicMock()
        run = self._make_run()
        db.query.return_value.filter.return_value.first.return_value = run
        db.query.return_value.filter.return_value.count.return_value = 0
        event_log.get_run_event_summary.return_value = {
            "has_run_started": True, "terminal_event_count": 0, "invariant_valid": True,
        }
        db.query.return_value.filter.return_value.all.return_value = []

        aggregator = TranslationResultAggregator(db, event_log)
        result = aggregator.get_run_status("run-1")

        assert result["language_stats"] == []

    def test_none_dates_returns_none(self):
        """None dates produce None isoformat."""
        db = MagicMock()
        event_log = MagicMock()
        run = self._make_run(started_at=None, completed_at=None)
        db.query.return_value.filter.return_value.first.return_value = run
        db.query.return_value.filter.return_value.count.return_value = 0
        event_log.get_run_event_summary.return_value = {
            "has_run_started": True, "terminal_event_count": 0, "invariant_valid": True,
        }
        db.query.return_value.filter.return_value.all.return_value = []

        aggregator = TranslationResultAggregator(db, event_log)
        result = aggregator.get_run_status("run-1")

        assert result["started_at"] is None
        assert result["completed_at"] is None

    def test_none_numeric_fields_defaults_zero(self):
        """Numeric fields default to 0 when None."""
        db = MagicMock()
        event_log = MagicMock()
        run = self._make_run(
            total_records=None, successful_records=None,
            failed_records=None, skipped_records=None, cache_hits=None,
        )
        db.query.return_value.filter.return_value.first.return_value = run
        db.query.return_value.filter.return_value.count.return_value = 0
        event_log.get_run_event_summary.return_value = {
            "has_run_started": True, "terminal_event_count": 0, "invariant_valid": True,
        }
        db.query.return_value.filter.return_value.all.return_value = []

        aggregator = TranslationResultAggregator(db, event_log)
        result = aggregator.get_run_status("run-1")

        assert result["total_records"] == 0
        assert result["successful_records"] == 0
        assert result["failed_records"] == 0
        assert result["skipped_records"] == 0
        assert result["cache_hits"] == 0


class TestGetRunRecords:
    """TranslationResultAggregator.get_run_records — delegates to orchestrator_query."""

    def test_delegates_to_query_function(self):
        """Calls get_run_records from orchestrator_query."""
        db = MagicMock()
        event_log = MagicMock()

        with patch("src.plugins.translate.orchestrator_aggregator._get_run_records") as mock_fn:
            mock_fn.return_value = {"items": [], "total": 0}
            aggregator = TranslationResultAggregator(db, event_log)
            result = aggregator.get_run_records("run-1", 2, 25, "failed", True)

        mock_fn.assert_called_once_with(db, "run-1", 2, 25, "failed", deduplicate=True)
        assert result == {"items": [], "total": 0}


class TestGetRunHistory:
    """TranslationResultAggregator.get_run_history — delegates to orchestrator_query."""

    def test_delegates_to_query_function(self):
        """Calls get_run_history from orchestrator_query."""
        db = MagicMock()
        event_log = MagicMock()

        with patch("src.plugins.translate.orchestrator_aggregator._get_run_history") as mock_fn:
            mock_fn.return_value = (5, [{"id": "r1"}])
            aggregator = TranslationResultAggregator(db, event_log)
            total, items = aggregator.get_run_history("job-1", 1, 20)

        mock_fn.assert_called_once_with(db, "job-1", 1, 20)
        assert total == 5
        assert items == [{"id": "r1"}]


class TestUpdateLanguageStats:
    """TranslationResultAggregator.update_language_stats — delegates to orchestrator_lang_stats."""

    def test_delegates_to_update_function(self):
        """Calls update_language_stats from orchestrator_lang_stats."""
        db = MagicMock()
        event_log = MagicMock()
        stats_map = {"ru": MagicMock()}

        with patch("src.plugins.translate.orchestrator_aggregator.update_language_stats") as mock_fn:
            aggregator = TranslationResultAggregator(db, event_log)
            aggregator.update_language_stats("run-1", stats_map)

        mock_fn.assert_called_once_with(
            db=db, event_log=event_log, run_id="run-1", language_stats_map=stats_map,
        )
# #endregion Test.OrchestratorAggregator
