# #region Test.OrchestratorPlanner [C:3] [TYPE Module] [SEMANTICS test, translate, planner, run, creation]
# @BRIEF Tests for orchestrator_planner.py — TranslationPlanner.plan_run.
# @RELATION BINDS_TO -> [TranslationPlanner]
# @TEST_CONTRACT: plan_run -> TranslationRun | creates run with config snapshot and hashes
# @TEST_EDGE: missing_job
# @TEST_EDGE: scheduled_trigger
# @TEST_EDGE: full_translation

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 unittest.mock import MagicMock, patch

import pytest

from src.plugins.translate.orchestrator_planner import TranslationPlanner


class TestTranslationPlanner:
    """TranslationPlanner.plan_run — create TranslationRun with hashes."""

    def _make_job(self, **kwargs):
        job = MagicMock()
        job.id = "job-1"
        job.source_dialect = "postgresql"
        job.target_dialect = "clickhouse"
        job.database_dialect = "postgresql"
        job.source_datasource_id = "ds-1"
        job.source_table = "source_table"
        job.target_schema = "target_schema"
        job.target_table = "target_table"
        job.source_key_cols = ["id"]
        job.target_key_cols = ["id"]
        job.translation_column = "name"
        job.target_column = "name_translated"
        job.context_columns = ["desc"]
        job.provider_id = "provider-1"
        job.batch_size = 50
        job.upsert_strategy = "MERGE"
        job.target_languages = ["ru", "de"]
        for k, v in kwargs.items():
            setattr(job, k, v)
        return job

    @patch("src.plugins.translate.orchestrator_planner.compute_config_hash")
    @patch("src.plugins.translate.orchestrator_planner.compute_dict_snapshot_hash")
    @patch("src.plugins.translate.orchestrator_planner.validate_job_preconditions")
    def test_plan_run_success(
        self, mock_validate, mock_dict_hash, mock_config_hash
    ):
        """Creates run with all required fields."""
        db = MagicMock()
        event_log = MagicMock()
        job = self._make_job()
        db.query.return_value.filter.return_value.first.return_value = job
        mock_config_hash.return_value = "config_hash_1234"
        mock_dict_hash.return_value = "dict_hash_5678"

        planner = TranslationPlanner(db, event_log, "user")
        result = planner.plan_run("job-1")

        assert result.job_id == "job-1"
        assert result.status == "PENDING"
        assert result.trigger_type == "manual"
        assert result.config_hash == "config_hash_1234"
        assert result.dict_snapshot_hash == "dict_hash_5678"
        assert result.key_hash is not None
        assert result.created_by == "user"
        assert result.config_snapshot is not None
        assert result.config_snapshot["source_dialect"] == "postgresql"
        assert result.config_snapshot["target_dialect"] == "clickhouse"
        assert result.config_snapshot["batch_size"] == 50
        assert result.config_snapshot["full_translation"] is False
        db.add.assert_called_once_with(result)
        db.flush.assert_called()
        db.commit.assert_called()
        db.refresh.assert_called_once_with(result)

    @patch("src.plugins.translate.orchestrator_planner.compute_config_hash")
    @patch("src.plugins.translate.orchestrator_planner.compute_dict_snapshot_hash")
    @patch("src.plugins.translate.orchestrator_planner.validate_job_preconditions")
    def test_missing_job_raises(
        self, mock_validate, mock_dict_hash, mock_config_hash
    ):
        """Missing job raises ValueError."""
        db = MagicMock()
        event_log = MagicMock()
        db.query.return_value.filter.return_value.first.return_value = None

        planner = TranslationPlanner(db, event_log, "user")
        with pytest.raises(ValueError, match="Translation job 'bad-id' not found"):
            planner.plan_run("bad-id")

    @patch("src.plugins.translate.orchestrator_planner.compute_config_hash")
    @patch("src.plugins.translate.orchestrator_planner.compute_dict_snapshot_hash")
    @patch("src.plugins.translate.orchestrator_planner.validate_job_preconditions")
    def test_scheduled_trigger(
        self, mock_validate, mock_dict_hash, mock_config_hash
    ):
        """is_scheduled=True sets trigger_type to 'scheduled'."""
        db = MagicMock()
        event_log = MagicMock()
        job = self._make_job()
        db.query.return_value.filter.return_value.first.return_value = job
        mock_config_hash.return_value = "hash1"
        mock_dict_hash.return_value = "hash2"

        planner = TranslationPlanner(db, event_log, "user")
        result = planner.plan_run("job-1", is_scheduled=True)

        assert result.trigger_type == "scheduled"

    @patch("src.plugins.translate.orchestrator_planner.compute_config_hash")
    @patch("src.plugins.translate.orchestrator_planner.compute_dict_snapshot_hash")
    @patch("src.plugins.translate.orchestrator_planner.validate_job_preconditions")
    def test_custom_trigger_type(
        self, mock_validate, mock_dict_hash, mock_config_hash
    ):
        """Custom trigger_type is preserved."""
        db = MagicMock()
        event_log = MagicMock()
        job = self._make_job()
        db.query.return_value.filter.return_value.first.return_value = job
        mock_config_hash.return_value = "hash1"
        mock_dict_hash.return_value = "hash2"

        planner = TranslationPlanner(db, event_log, "user")
        result = planner.plan_run("job-1", trigger_type="api_webhook")

        assert result.trigger_type == "api_webhook"

    @patch("src.plugins.translate.orchestrator_planner.compute_config_hash")
    @patch("src.plugins.translate.orchestrator_planner.compute_dict_snapshot_hash")
    @patch("src.plugins.translate.orchestrator_planner.validate_job_preconditions")
    def test_full_translation_flag(
        self, mock_validate, mock_dict_hash, mock_config_hash
    ):
        """full_translation=True sets flag in config_snapshot."""
        db = MagicMock()
        event_log = MagicMock()
        job = self._make_job()
        db.query.return_value.filter.return_value.first.return_value = job
        mock_config_hash.return_value = "hash1"
        mock_dict_hash.return_value = "hash2"

        planner = TranslationPlanner(db, event_log, "user")
        result = planner.plan_run("job-1", full_translation=True)

        assert result.config_snapshot["full_translation"] is True

    @patch("src.plugins.translate.orchestrator_planner.compute_config_hash")
    @patch("src.plugins.translate.orchestrator_planner.compute_dict_snapshot_hash")
    @patch("src.plugins.translate.orchestrator_planner.validate_job_preconditions")
    def test_event_logged_on_creation(
        self, mock_validate, mock_dict_hash, mock_config_hash
    ):
        """RUN_STARTED event logged after run creation."""
        db = MagicMock()
        event_log = MagicMock()
        job = self._make_job()
        db.query.return_value.filter.return_value.first.return_value = job
        mock_config_hash.return_value = "ch"
        mock_dict_hash.return_value = "dh"

        planner = TranslationPlanner(db, event_log, "user")
        result = planner.plan_run("job-1")

        event_log.log_event.assert_called_once()
        call_args = event_log.log_event.call_args[1]
        assert call_args["event_type"] == "RUN_STARTED"
        assert call_args["run_id"] == result.id
        assert call_args["payload"]["config_hash"] == "ch"

    @patch("src.plugins.translate.orchestrator_planner.compute_config_hash")
    @patch("src.plugins.translate.orchestrator_planner.compute_dict_snapshot_hash")
    @patch("src.plugins.translate.orchestrator_planner.validate_job_preconditions")
    def test_validates_preconditions(
        self, mock_validate, mock_dict_hash, mock_config_hash
    ):
        """Calls validate_job_preconditions with correct args."""
        db = MagicMock()
        event_log = MagicMock()
        job = self._make_job()
        db.query.return_value.filter.return_value.first.return_value = job
        mock_config_hash.return_value = "ch"
        mock_dict_hash.return_value = "dh"

        planner = TranslationPlanner(db, event_log, "user")
        planner.plan_run("job-1", is_scheduled=True)

        mock_validate.assert_called_once_with(job, db, is_scheduled=True)

    @patch("src.plugins.translate.orchestrator_planner.compute_config_hash")
    @patch("src.plugins.translate.orchestrator_planner.compute_dict_snapshot_hash")
    @patch("src.plugins.translate.orchestrator_planner.validate_job_preconditions")
    def test_config_snapshot_includes_all_fields(
        self, mock_validate, mock_dict_hash, mock_config_hash
    ):
        """Config snapshot contains all relevant job fields."""
        db = MagicMock()
        event_log = MagicMock()
        job = self._make_job()
        db.query.return_value.filter.return_value.first.return_value = job
        mock_config_hash.return_value = "ch"
        mock_dict_hash.return_value = "dh"

        planner = TranslationPlanner(db, event_log, "user")
        result = planner.plan_run("job-1")

        snapshot = result.config_snapshot
        assert snapshot["source_dialect"] == "postgresql"
        assert snapshot["target_dialect"] == "clickhouse"
        assert snapshot["source_datasource_id"] == "ds-1"
        assert snapshot["source_table"] == "source_table"
        assert snapshot["target_schema"] == "target_schema"
        assert snapshot["target_table"] == "target_table"
        assert snapshot["source_key_cols"] == ["id"]
        assert snapshot["translation_column"] == "name"
        assert snapshot["provider_id"] == "provider-1"
        assert snapshot["upsert_strategy"] == "MERGE"
# #endregion Test.OrchestratorPlanner
