"""Integration tests for scheduler features.

Tests end-to-end scheduler workflows including:
- Cron job scheduling
- Recurring job execution
- Timezone handling
- Job dependency scheduling
"""

from __future__ import annotations

from datetime import datetime, timedelta

import pytest

from oclawma.queue.models import JobPriority
from oclawma.queue.queue import JobQueue
from oclawma.scheduler.cron import get_next_run, validate_cron


@pytest.mark.integration
class TestCronScheduling:
    """End-to-end tests for cron scheduling."""

    def test_parse_simple_cron_expression(self):
        """Test parsing simple cron expressions."""
        # Every minute
        result = validate_cron("* * * * *")
        assert result is True

        # Every hour at minute 0
        result = validate_cron("0 * * * *")
        assert result is True

        # Every day at midnight
        result = validate_cron("0 0 * * *")
        assert result is True

    def test_cron_next_run_calculation(self):
        """Test calculating next run time from cron expression."""
        now = datetime.utcnow()

        # Every minute - should be within next minute
        next_run = get_next_run("* * * * *")
        assert next_run is not None
        assert next_run > now
        assert next_run <= now + timedelta(minutes=1)

    def test_enqueue_cron_job(self, in_memory_queue: JobQueue):
        """Test enqueueing a job with cron schedule."""
        queue = in_memory_queue

        job = queue.enqueue(
            {"task": "hourly_backup"},
            cron_expression="0 * * * *",
            timezone="UTC",
        )

        assert job.cron_expression == "0 * * * *"
        assert job.timezone == "UTC"
        assert job.next_run_at is not None

    def test_cron_job_not_due_yet(self, in_memory_queue: JobQueue):
        """Test that future cron jobs are not dequeued."""
        queue = in_memory_queue

        # Schedule for tomorrow
        tomorrow = datetime.utcnow() + timedelta(days=1)
        cron_expr = f"{tomorrow.minute} {tomorrow.hour} {tomorrow.day} {tomorrow.month} *"

        queue.enqueue(
            {"task": "future"},
            cron_expression=cron_expr,
        )

        # Should not be available yet
        dequeued, _ = queue.dequeue_with_rate_limit()
        assert dequeued is None


@pytest.mark.integration
class TestJobScheduling:
    """End-to-end tests for job scheduling features."""

    def test_delayed_job_execution(self, in_memory_queue: JobQueue):
        """Test that delayed jobs execute at scheduled time."""
        queue = in_memory_queue

        # Schedule for 1 second ago (should be ready)
        past_time = datetime.utcnow() - timedelta(seconds=1)

        job = queue.enqueue(
            {"task": "delayed"},
            scheduled_at=past_time,
        )

        # Should be available
        dequeued, _ = queue.dequeue_with_rate_limit()
        assert dequeued is not None
        assert dequeued.id == job.id

    def test_future_job_not_available(self, in_memory_queue: JobQueue):
        """Test that future jobs are not available before scheduled time."""
        queue = in_memory_queue

        # Schedule for future
        future_time = datetime.utcnow() + timedelta(hours=1)

        queue.enqueue(
            {"task": "future"},
            scheduled_at=future_time,
        )

        # Should not be available
        dequeued, _ = queue.dequeue_with_rate_limit()
        assert dequeued is None

        stats = queue.get_stats()
        assert stats.pending == 1

    def test_schedule_priority_over_time(self, in_memory_queue: JobQueue):
        """Test that priority still applies within scheduled jobs."""
        queue = in_memory_queue

        past_time = datetime.utcnow() - timedelta(minutes=5)

        # Add scheduled jobs with different priorities
        queue.enqueue(
            {"task": "low"},
            priority=JobPriority.LOW,
            scheduled_at=past_time,
        )

        queue.enqueue(
            {"task": "high"},
            priority=JobPriority.HIGH,
            scheduled_at=past_time,
        )

        # High priority should come first
        dequeued, _ = queue.dequeue_with_rate_limit()
        assert dequeued is not None
        assert dequeued.payload.get("task") == "high"


@pytest.mark.integration
class TestTimezoneHandling:
    """End-to-end tests for timezone handling in scheduling."""

    def test_timezone_aware_scheduling(self, in_memory_queue: JobQueue):
        """Test that timezones are handled correctly."""
        queue = in_memory_queue

        # This is a basic test - in production you'd use pytz
        job = queue.enqueue(
            {"task": "tz_test"},
            cron_expression="0 9 * * *",
            timezone="America/New_York",
        )

        assert job.timezone == "America/New_York"


@pytest.mark.integration
class TestSchedulerWithDependencies:
    """End-to-end tests for scheduling with dependencies."""

    def test_scheduled_dependency_chain(self, in_memory_queue: JobQueue):
        """Test scheduled jobs with dependencies."""
        queue = in_memory_queue

        past_time = datetime.utcnow() - timedelta(minutes=1)

        # Create parent job (scheduled in past)
        parent = queue.enqueue(
            {"task": "parent"},
            scheduled_at=past_time,
        )

        # Create child job with dependency
        queue.enqueue(
            {"task": "child"},
            depends_on=[parent.id],
        )

        # Child should be blocked even though parent is ready
        first, _ = queue.dequeue_with_rate_limit()
        assert first is not None
        assert first.payload.get("task") == "parent"

        # Complete parent
        queue.complete(first.id)

        # Now child should be available
        second, _ = queue.dequeue_with_rate_limit()
        assert second is not None
        assert second.payload.get("task") == "child"

    def test_delayed_dependency_completion(self, in_memory_queue: JobQueue):
        """Test that dependencies work with delayed completion."""
        queue = in_memory_queue

        # Create parent
        parent = queue.enqueue({"task": "slow_parent"})

        # Create child with dependency
        queue.enqueue(
            {"task": "waiting_child"},
            depends_on=[parent.id],
        )

        # Start parent but don't complete
        started, _ = queue.dequeue_with_rate_limit()
        assert started.payload.get("task") == "slow_parent"

        # Child should still be blocked
        blocked, _ = queue.dequeue_with_rate_limit()
        assert blocked is None

        # Complete parent
        queue.complete(parent.id)

        # Child should now be available
        unblocked, _ = queue.dequeue_with_rate_limit()
        assert unblocked is not None
        assert unblocked.payload.get("task") == "waiting_child"
