"""Scheduler for recurring cron jobs.

This module extends the job queue with cron-based recurring job support.
"""

from __future__ import annotations

import logging
from datetime import datetime
from typing import TYPE_CHECKING, Any

from oclawma.scheduler.cron import CronError, CronExpression, get_next_run

if TYPE_CHECKING:
    from oclawma.queue.models import Job
    from oclawma.queue.queue import JobQueue

logger = logging.getLogger(__name__)


def schedule_recurring_job(
    queue: JobQueue,
    payload: dict[str, Any],
    cron_expression: str,
    timezone: str = "UTC",
    priority: Any = None,
    max_retries: int | None = None,
) -> Job:
    """Schedule a recurring job with cron expression.

    Args:
        queue: The job queue
        payload: Job payload data
        cron_expression: Cron expression (e.g., "0 0 * * *" for daily at midnight)
        timezone: Timezone for scheduling (default: UTC)
        priority: Job priority (defaults to JobPriority.NORMAL if None)
        max_retries: Max retries for this job

    Returns:
        The created job

    Raises:
        CronError: If cron expression is invalid
    """
    from oclawma.queue.models import JobPriority

    # Validate cron expression
    cron = CronExpression(cron_expression)

    # Calculate next run time
    next_run = cron.get_next_run()

    # Create job with cron fields
    # Use NORMAL priority if none provided
    job_priority = priority if priority is not None else JobPriority.NORMAL

    job = queue.enqueue(
        payload=payload,
        priority=job_priority,
        max_retries=max_retries,
    )

    # Update job with cron fields
    job.cron_expression = cron_expression
    job.timezone = timezone
    job.next_run_at = next_run

    # Update in database
    queue.store.update(job)

    logger.info(
        f"Scheduled recurring job {job.id} with cron '{cron_expression}' next run at {next_run}"
    )
    return job


def reschedule_job(queue: JobQueue, job_id: int) -> Job | None:
    """Reschedule a recurring job after it completes.

    Calculates the next run time based on the job's cron expression
    and updates next_run_at.

    Args:
        queue: The job queue
        job_id: Job ID to reschedule

    Returns:
        Updated job or None if job is not recurring

    Raises:
        JobNotFoundError: If job not found
    """
    from oclawma.queue.store import JobNotFoundError

    try:
        job = queue.get_job(job_id)
    except JobNotFoundError:
        raise

    if not job.cron_expression:
        logger.debug(f"Job {job_id} is not a recurring job")
        return None

    try:
        # Calculate next run time
        next_run = get_next_run(job.cron_expression)
        job.next_run_at = next_run
        from oclawma.queue.models import JobStatus

        job.status = JobStatus.PENDING  # Reset to pending for next run
        job.started_at = None
        job.completed_at = None
        job.error = None
        job.updated_at = datetime.utcnow()

        # Update in database
        updated = queue.store.update(job)

        logger.info(f"Rescheduled recurring job {job_id} next run at {next_run}")
        return updated

    except CronError as e:
        logger.error(f"Failed to reschedule job {job_id}: {e}")
        raise


def get_due_jobs(queue: JobQueue, limit: int | None = None) -> list[Job]:
    """Get recurring jobs that are due to run.

    Args:
        queue: The job queue
        limit: Maximum number of jobs to return

    Returns:
        List of due recurring jobs
    """
    return queue.store.get_due_jobs(limit)


def process_due_jobs(queue: JobQueue, handler: Any, limit: int | None = None) -> int:
    """Process all due recurring jobs.

    Args:
        queue: The job queue
        handler: Function to process each job
        limit: Maximum number of jobs to process

    Returns:
        Number of jobs processed
    """
    due_jobs = get_due_jobs(queue, limit)
    count = 0

    for job in due_jobs:
        try:
            from oclawma.queue.models import JobStatus

            # Update job to running status
            job.status = JobStatus.RUNNING
            job.started_at = datetime.utcnow()
            job.updated_at = datetime.utcnow()
            queue.store.update(job)

            # Process the job
            handler(job)

            # Complete the job and reschedule
            job.status = JobStatus.COMPLETED
            job.completed_at = datetime.utcnow()
            job.updated_at = datetime.utcnow()
            queue.store.update(job)

            # Reschedule for next occurrence
            reschedule_job(queue, job.id)
            count += 1

        except Exception as e:
            logger.error(f"Failed to process due job {job.id}: {e}")
            # Mark as failed
            from oclawma.queue.models import JobStatus

            job.status = JobStatus.FAILED
            job.error = str(e)
            job.completed_at = datetime.utcnow()
            job.updated_at = datetime.utcnow()
            queue.store.update(job)

    return count
