"""Tests for scheduler.py — automated pipeline scheduling.

TDD: write failing test, implement, refactor.
"""

from __future__ import annotations

import signal
from unittest.mock import MagicMock, patch

import pytest


class TestPipelineSchedulerInit:
    def test_default_db_path(self):
        from scheduler import PipelineScheduler

        sched = PipelineScheduler()
        assert sched.db_path == "data/motions.db"
        assert not sched._running

    def test_custom_db_path(self):
        from scheduler import PipelineScheduler

        sched = PipelineScheduler(db_path="/tmp/test.db")
        assert sched.db_path == "/tmp/test.db"


class TestPipelineSchedulerRunPipeline:
    def test_calls_pipeline_run_with_db_path(self):
        from scheduler import PipelineScheduler

        sched = PipelineScheduler(db_path="/tmp/test.db")
        with patch("scheduler.run_pipeline") as mock_run:
            mock_run.return_value = 0
            sched.run_pipeline()
            mock_run.assert_called_once()
            # Verify args contain db_path via Namespace
            args = mock_run.call_args[0][0]
            assert args.db_path == "/tmp/test.db"

    def test_logs_error_on_pipeline_failure(self):
        from scheduler import PipelineScheduler

        sched = PipelineScheduler()
        with patch("scheduler.run_pipeline") as mock_run:
            mock_run.side_effect = RuntimeError("pipeline failed")
            with patch("scheduler._logger") as mock_logger:
                result = sched.run_pipeline()
                assert result == 1
                mock_logger.exception.assert_called_once()


class TestPipelineSchedulerRunSummarizer:
    def test_calls_summarizer_update(self):
        from scheduler import PipelineScheduler

        sched = PipelineScheduler()
        with patch("scheduler.summarizer") as mock_summarizer:
            sched.run_summarizer()
            mock_summarizer.update_motion_summaries.assert_called_once()

    def test_logs_error_on_summarizer_failure(self):
        from scheduler import PipelineScheduler

        sched = PipelineScheduler()
        with patch("scheduler.summarizer") as mock_summarizer:
            mock_summarizer.update_motion_summaries.side_effect = RuntimeError(
                "summarizer failed"
            )
            with patch("scheduler._logger") as mock_logger:
                sched.run_summarizer()
                mock_logger.exception.assert_called_once()


class TestPipelineSchedulerSchedule:
    def test_schedule_daily_adds_job(self):
        from scheduler import PipelineScheduler

        sched = PipelineScheduler()
        with patch("scheduler.schedule") as mock_schedule:
            mock_job = MagicMock()
            mock_schedule.every.return_value.day.at.return_value.do = mock_job
            sched.schedule_daily("02:00")
            mock_schedule.every.assert_called_once()

    def test_schedule_summarizer_adds_job(self):
        from scheduler import PipelineScheduler

        sched = PipelineScheduler()
        with patch("scheduler.schedule") as mock_schedule:
            mock_job = MagicMock()
            mock_schedule.every.return_value.hour.do = mock_job
            sched.schedule_summarizer(every_n_hours=6)
            mock_schedule.every.assert_called_once()


class TestPipelineSchedulerLoop:
    def test_start_runs_pending_jobs(self):
        from scheduler import PipelineScheduler

        sched = PipelineScheduler()
        call_count = 0

        def _stop_after_first(*args, **kwargs):
            nonlocal call_count
            call_count += 1
            if call_count >= 3:
                sched.stop()

        with patch("scheduler.schedule.run_pending") as mock_run_pending:
            with patch("scheduler.time.sleep", side_effect=_stop_after_first):
                with patch("scheduler.signal.signal"):
                    sched.start()
                    assert mock_run_pending.called
                    assert not sched._running

    def test_stop_sets_running_false(self):
        from scheduler import PipelineScheduler

        sched = PipelineScheduler()
        sched._running = True
        sched.stop()
        assert not sched._running

    def test_signal_handler_stops_scheduler(self):
        from scheduler import PipelineScheduler

        sched = PipelineScheduler()
        sched._running = True
        with patch.object(sched, "stop") as mock_stop:
            sched._signal_handler(signal.SIGINT, None)
            mock_stop.assert_called_once()


class TestSchedulerCLI:
    def test_main_parses_args(self):
        from scheduler import main

        with patch("scheduler.PipelineScheduler") as mock_sched_class:
            mock_sched = MagicMock()
            mock_sched_class.return_value = mock_sched
            rc = main(["--pipeline-time", "03:00"])
            assert rc == 0
            mock_sched_class.assert_called_once_with(db_path="data/motions.db")
            mock_sched.schedule_daily.assert_called_once_with("03:00")
            mock_sched.start.assert_called_once()

    def test_main_custom_db_path(self):
        from scheduler import main

        with patch("scheduler.PipelineScheduler") as mock_sched_class:
            mock_sched = MagicMock()
            mock_sched.run_pipeline.return_value = 0
            mock_sched_class.return_value = mock_sched
            rc = main(["--db-path", "/tmp/test.db", "--once"])
            assert rc == 0
            mock_sched_class.assert_called_once_with(db_path="/tmp/test.db")
            mock_sched.run_pipeline.assert_called_once()
