# #region Test.ValidationTaskService.Triggers [C:3] [TYPE Module] [SEMANTICS test,validation,trigger,run,detail]
# @BRIEF Tests for validation_service.py — trigger runs, list all runs, run detail.
# @RELATION BINDS_TO -> [ValidationService]

from pathlib import Path
import sys

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

from unittest.mock import AsyncMock, MagicMock, patch
from datetime import UTC, datetime
import pytest


def make_policy(**overrides):
    policy = MagicMock()
    policy.id = "pol-1"
    policy.name = "Test Task"
    policy.description = None
    policy.environment_id = "env-1"
    policy.is_active = True
    policy.dashboard_ids = []
    policy.provider_id = "prov-1"
    policy.schedule_days = None
    policy.window_start = None
    policy.window_end = None
    policy.notify_owners = None
    policy.custom_channels = None
    policy.alert_condition = None
    policy.created_at = datetime(2026, 1, 1, 12, 0, 0, tzinfo=UTC)
    policy.updated_at = datetime(2026, 1, 1, 12, 0, 0, tzinfo=UTC)
    policy.sources = []
    policy.screenshot_enabled = True
    policy.logs_enabled = True
    policy.execute_chart_data = False
    policy.llm_batch_size = 1
    policy.policy_dashboard_concurrency_limit = 3
    policy.prompt_template = None
    for k, v in overrides.items():
        setattr(policy, k, v)
    return policy


def make_valid_query_mock(policies=None):
    m = MagicMock()
    policies = policies or []
    first_result = policies[0] if policies else None
    m.configure_mock(**{"count.return_value": len(policies)})
    m.configure_mock(**{"order_by.return_value.offset.return_value.limit.return_value.all.return_value": policies})
    m.configure_mock(**{"filter.return_value.first.return_value": first_result})
    m.configure_mock(**{"filter.return_value.count.return_value": len(policies)})
    m.configure_mock(**{"filter.return_value.order_by.return_value.first.return_value": None})
    m.configure_mock(**{"filter.return_value.order_by.return_value.offset.return_value.limit.return_value.all.return_value": policies})
    return m


class TestTriggerRun:
    """trigger_run — create run and spawn task."""

    @pytest.mark.asyncio
    async def test_not_found(self):
        from src.services.validation_service import ValidationTaskService
        db = MagicMock()
        db.query.return_value.filter.return_value.first.return_value = None
        svc = ValidationTaskService(db)
        with pytest.raises(ValueError, match="not found"):
            await svc.trigger_run("nonexistent", AsyncMock())

    @pytest.mark.asyncio
    async def test_existing_running_run(self):
        from src.services.validation_service import ValidationTaskService
        policy = make_policy(dashboard_ids=["42", "99"])
        db = MagicMock()
        mock_q = MagicMock()
        mock_q.filter.return_value.first.side_effect = [policy, MagicMock()]
        db.query.return_value = mock_q
        svc = ValidationTaskService(db)
        with pytest.raises(ValueError, match="already has a running run"):
            await svc.trigger_run("pol-1", AsyncMock())

    @pytest.mark.asyncio
    async def test_worker_limit_reached(self):
        from src.services.validation_service import ValidationTaskService
        policy = make_policy(dashboard_ids=["42"], screenshot_enabled=True)
        db = MagicMock()
        call_log = {"count": 0}

        def query_fn(model):
            if model.__name__ == "ValidationPolicy":
                m = MagicMock()
                m.filter.return_value.first.return_value = policy
                return m
            elif model.__name__ == "ValidationRun":
                m = MagicMock()
                def run_filter(*a, **kw):
                    call_log["count"] += 1
                    fm = MagicMock()
                    if call_log["count"] == 1:
                        fm.first.return_value = None
                    else:
                        fm.count.return_value = 5
                    return fm
                m.filter = run_filter
                return m
            else:
                m = MagicMock()
                m.filter.return_value.all.return_value = []
                return m

        db.query = query_fn
        cm = MagicMock()
        cm.configure_mock(**{"config.settings.global_validation_worker_limit": 3})
        svc = ValidationTaskService(db, config_manager=cm)
        with pytest.raises(ValueError, match="worker limit"):
            await svc.trigger_run("pol-1", AsyncMock())

    @pytest.mark.asyncio
    async def test_no_dashboard_ids_raises(self):
        from src.services.validation_service import ValidationTaskService
        policy = make_policy(dashboard_ids=[], screenshot_enabled=True)
        db = MagicMock()

        def query_fn(model):
            m = MagicMock()
            if model.__name__ == "ValidationPolicy":
                m.filter.return_value.first.return_value = policy
            elif model.__name__ == "ValidationRun":
                m.filter.return_value.first.return_value = None
                m.filter.return_value.count.return_value = 0
            elif model.__name__ == "ValidationSource":
                m.filter.return_value.all.return_value = []
            return m

        db.query = query_fn
        cm = MagicMock()
        cm.configure_mock(**{"config.settings.global_validation_worker_limit": 5})
        svc = ValidationTaskService(db, config_manager=cm)
        with patch("src.services.validation_service.LLMProviderService") as mock_cls:
            mock_svc = MagicMock()
            provider = MagicMock()
            provider.is_active = True
            provider.is_multimodal = True
            mock_svc.get_provider.return_value = provider
            mock_cls.return_value = mock_svc
            with pytest.raises(ValueError, match="no dashboard IDs"):
                await svc.trigger_run("pol-1", AsyncMock())

    @pytest.mark.asyncio
    async def test_successful_trigger(self):
        from src.services.validation_service import ValidationTaskService
        policy = make_policy(
            dashboard_ids=["42", "99"],
            screenshot_enabled=True, logs_enabled=True,
            execute_chart_data=False, llm_batch_size=1,
            policy_dashboard_concurrency_limit=3, prompt_template=None,
        )
        db = MagicMock()

        def query_fn(model):
            m = MagicMock()
            if model.__name__ == "ValidationSource":
                m.filter.return_value.all.return_value = []
            elif model.__name__ == "ValidationRun":
                m.filter.return_value.first.return_value = None
                m.filter.return_value.count.return_value = 0
            elif model.__name__ == "ValidationPolicy":
                m.filter.return_value.first.return_value = policy
            return m

        db.query = query_fn
        cm = MagicMock()
        cm.configure_mock(**{"config.settings.global_validation_worker_limit": 5})
        svc = ValidationTaskService(db, config_manager=cm, username="tester")

        with patch("src.services.validation_service.LLMProviderService") as mock_cls:
            mock_svc = MagicMock()
            provider = MagicMock()
            provider.is_active = True
            provider.is_multimodal = True
            mock_svc.get_provider.return_value = provider
            mock_cls.return_value = mock_svc

            task_manager = AsyncMock()
            task = MagicMock()
            task.id = "spawned-task-1"
            task_manager.create_task.return_value = task
            result = await svc.trigger_run("pol-1", task_manager)

        assert result["task_id"] == "spawned-task-1"
        assert "run_id" in result

    @pytest.mark.asyncio
    async def test_trigger_with_sources_and_no_dashboard_ids(self):
        """Cover trigger_run line 641: sources present with resolved_dashboard_ids."""
        from src.services.validation_service import ValidationTaskService
        policy = make_policy(
            dashboard_ids=[],
            screenshot_enabled=True, logs_enabled=True,
            execute_chart_data=False, llm_batch_size=1,
            policy_dashboard_concurrency_limit=3, prompt_template=None,
        )
        db = MagicMock()

        # Create source mock with resolved_dashboard_id
        source_mock = MagicMock()
        source_mock.resolved_dashboard_id = "42"
        source_mock.value = "http://superset/d/42"

        def query_fn(model):
            m = MagicMock()
            if model.__name__ == "ValidationSource":
                m.filter.return_value.all.return_value = [source_mock]
            elif model.__name__ == "ValidationRun":
                m.filter.return_value.first.return_value = None
                m.filter.return_value.count.return_value = 0
            elif model.__name__ == "ValidationPolicy":
                m.filter.return_value.first.return_value = policy
            return m

        db.query = query_fn
        cm = MagicMock()
        cm.configure_mock(**{"config.settings.global_validation_worker_limit": 5})
        svc = ValidationTaskService(db, config_manager=cm, username="tester")

        with patch("src.services.validation_service.LLMProviderService") as mock_cls:
            mock_svc = MagicMock()
            provider = MagicMock()
            provider.is_active = True
            provider.is_multimodal = True
            mock_svc.get_provider.return_value = provider
            mock_cls.return_value = mock_svc

            task_manager = AsyncMock()
            task = MagicMock()
            task.id = "spawned-task-1"
            task_manager.create_task.return_value = task
            result = await svc.trigger_run("pol-1", task_manager)

        assert result["task_id"] == "spawned-task-1"
        assert "run_id" in result


class TestListAllRuns:
    """list_all_runs — cross-task filtering."""

    def make_record(self, **overrides):
        from src.models.llm import ValidationRecord as VR
        r = MagicMock(spec=VR)
        r.id = "rec-1"
        r.run_id = "run-1"
        r.policy_id = "pol-1"
        r.dashboard_id = "42"
        r.dashboard_title = "Sales"
        r.environment_id = "env-1"
        r.status = "PASS"
        r.summary = "All good"
        r.issues = []
        r.raw_response = None
        r.screenshot_path = None
        r.screenshot_paths = None
        r.execution_path = None
        r.dataset_health = None
        r.chart_data_results = []
        r.tab_screenshots = []
        r.logs_sent_to_llm = []
        r.token_usage = None
        r.timings = None
        r.timestamp = datetime(2026, 1, 1, 12, 0, 0, tzinfo=UTC)
        for k, v in overrides.items():
            setattr(r, k, v)
        return r

    @pytest.mark.asyncio
    async def test_no_filters(self):
        from src.services.validation_service import ValidationTaskService
        record = self.make_record()
        db = MagicMock()
        m = MagicMock()
        m.configure_mock(**{"count.return_value": 1})
        m.configure_mock(**{"order_by.return_value.offset.return_value.limit.return_value.all.return_value": [record]})
        m.configure_mock(**{"filter.return_value.count.return_value": 1})
        m.configure_mock(**{"filter.return_value.order_by.return_value.offset.return_value.limit.return_value.all.return_value": [record]})
        db.query.return_value = m
        svc = ValidationTaskService(db)
        total, records = await svc.list_all_runs()
        assert total == 1
        assert len(records) == 1
        assert records[0]["status"] == "PASS"

    @pytest.mark.asyncio
    async def test_filter_by_status(self):
        from src.services.validation_service import ValidationTaskService
        db = MagicMock()
        svc = ValidationTaskService(db)
        await svc.list_all_runs(status="FAIL")
        db.query.return_value.filter.assert_called()

    @pytest.mark.asyncio
    async def test_filter_by_environment(self):
        from src.services.validation_service import ValidationTaskService
        db = MagicMock()
        svc = ValidationTaskService(db)
        await svc.list_all_runs(environment_id="env-1")

    @pytest.mark.asyncio
    async def test_filter_by_dashboard_id(self):
        from src.services.validation_service import ValidationTaskService
        db = MagicMock()
        svc = ValidationTaskService(db)
        await svc.list_all_runs(dashboard_id="42")


class TestGetRunDetail:
    """get_run_detail — run + records."""

    @pytest.mark.asyncio
    async def test_not_found(self):
        from src.services.validation_service import ValidationTaskService
        db = MagicMock()
        db.query.return_value.filter.return_value.first.return_value = None
        svc = ValidationTaskService(db)
        with pytest.raises(ValueError, match="not found"):
            await svc.get_run_detail("nonexistent")

    @pytest.mark.asyncio
    async def test_success(self):
        from src.services.validation_service import ValidationTaskService
        run = MagicMock()
        run.id = "run-1"
        run.policy_id = "pol-1"
        run.task_id = "task-1"
        run.status = "completed"
        run.trigger = "manual"
        run.started_at = None
        run.finished_at = None
        run.dashboard_count = 0
        run.pass_count = 0
        run.warn_count = 0
        run.fail_count = 0
        run.unknown_count = 0
        run.created_at = datetime(2026, 1, 1, 12, 0, 0, tzinfo=UTC)

        from src.models.llm import ValidationRecord as VR
        record = MagicMock(spec=VR)
        record.id = "rec-1"
        record.run_id = "run-1"
        record.policy_id = "pol-1"
        record.dashboard_id = "42"
        record.dashboard_title = "Sales"
        record.environment_id = "env-1"
        record.status = "PASS"
        record.summary = "OK"
        record.issues = []
        record.raw_response = None
        record.screenshot_path = None
        record.screenshot_paths = None
        record.execution_path = None
        record.dataset_health = None
        record.chart_data_results = []
        record.tab_screenshots = []
        record.logs_sent_to_llm = []
        record.token_usage = None
        record.timings = None
        record.timestamp = datetime(2026, 1, 1, 12, 0, 0, tzinfo=UTC)

        db = MagicMock()
        db.query.return_value.filter.return_value.first.return_value = run
        db.query.return_value.filter.return_value.order_by.return_value.all.return_value = [record]
        svc = ValidationTaskService(db)
        result = await svc.get_run_detail("run-1")
        assert result["run"]["status"] == "completed"
        assert len(result["records"]) == 1
        assert result["records"][0]["status"] == "PASS"
# #endregion Test.ValidationTaskService.Triggers
