"""Tracked job queue wrapper that automatically collects metrics."""

from __future__ import annotations

from datetime import datetime
from typing import Any, Callable

from oclawma.metrics.collector import MetricsCollector
from oclawma.queue import Job, JobQueue, JobStatus


class TrackedJobQueue:
    """Wrapper around JobQueue that automatically tracks metrics.

    This class wraps a JobQueue and automatically updates metrics
    when jobs are enqueued, dequeued, completed, or failed.

    Example:
        >>> from oclawma.metrics import MetricsCollector, TrackedJobQueue
        >>> from oclawma.queue import JobQueue
        >>> queue = JobQueue("/path/to/queue.db")
        >>> collector = MetricsCollector()
        >>> tracked = TrackedJobQueue(queue, collector)
        >>> job = tracked.enqueue({"task": "test"})  # Updates metrics
        >>> job = tracked.dequeue()  # Updates metrics
        >>> tracked.complete(job.id)  # Records duration, updates counters
    """

    def __init__(self, queue: JobQueue, collector: MetricsCollector) -> None:
        """Initialize tracked queue.

        Args:
            queue: Underlying JobQueue instance
            collector: Metrics collector to update
        """
        self.queue = queue
        self.collector = collector
        self._job_start_times: dict[int, float] = {}

    def enqueue(
        self,
        payload: dict[str, Any],
        priority: int = 0,
        scheduled_at: datetime | None = None,
        max_retries: int | None = None,
    ) -> Job:
        """Add a new job to the queue and update metrics.

        Args:
            payload: Job data/payload
            priority: Job priority (higher = more important)
            scheduled_at: When to run the job (None = run ASAP)
            max_retries: Max retries for this job (None = use default)

        Returns:
            The created job
        """
        job = self.queue.enqueue(
            payload=payload,
            priority=priority,
            scheduled_at=scheduled_at,
            max_retries=max_retries,
        )

        # Update queue depth
        self._update_queue_metrics()

        return job

    def dequeue(self, block: bool = False, timeout: float | None = None) -> Job | None:
        """Get the next job to process and update metrics.

        Args:
            block: If True, block until a job is available
            timeout: Max seconds to block (None = forever)

        Returns:
            Next job to process, or None if no jobs available
        """
        job = self.queue.dequeue(block=block, timeout=timeout)

        if job:
            # Track start time for duration calculation
            import time

            self._job_start_times[job.id] = time.perf_counter()

            # Update metrics
            self.collector.increment_processed()
            self._update_queue_metrics()

        return job

    def complete(self, job_id: int) -> Job:
        """Mark a job as completed and update metrics.

        Args:
            job_id: Job identifier

        Returns:
            The updated job
        """
        job = self.queue.complete(job_id)

        # Record duration if we have start time
        self._record_duration(job_id)

        # Update counters
        self.collector.increment_completed()
        self._update_queue_metrics()

        return job

    def fail(self, job_id: int, error: str | None = None) -> Job:
        """Mark a job as failed and update metrics.

        Args:
            job_id: Job identifier
            error: Error message

        Returns:
            The updated job
        """
        job = self.queue.fail(job_id, error)

        # Record duration if we have start time
        self._record_duration(job_id)

        # Update counters
        self.collector.increment_failed()
        self._update_queue_metrics()

        return job

    def retry(self, job_id: int) -> Job | None:
        """Retry a failed job and update metrics.

        Args:
            job_id: Job identifier

        Returns:
            Updated job if retry scheduled, None if max retries exceeded
        """
        job = self.queue.retry(job_id)

        if job:
            self._update_queue_metrics()

        return job

    def cancel(self, job_id: int) -> bool:
        """Cancel a pending or running job and update metrics.

        Args:
            job_id: Job identifier

        Returns:
            True if cancelled, False if not found or not cancellable
        """
        result = self.queue.cancel(job_id)

        if result:
            self._update_queue_metrics()

        return result

    def get_job(self, job_id: int) -> Job:
        """Get a job by id.

        Args:
            job_id: Job identifier

        Returns:
            The job
        """
        return self.queue.get_job(job_id)

    def get_stats(self) -> Any:
        """Get queue statistics."""
        return self.queue.get_stats()

    def list_jobs(self, status: JobStatus | None = None, limit: int | None = None) -> list[Job]:
        """List jobs in the queue.

        Args:
            status: Filter by status
            limit: Max jobs to return

        Returns:
            List of jobs
        """
        return self.queue.list_jobs(status=status, limit=limit)

    def process_next(self, handler: Callable[[Job], None]) -> bool:
        """Process the next available job with automatic metric tracking.

        This wraps the handler with timing and automatically updates
        success/failure metrics.

        Args:
            handler: Function to process the job

        Returns:
            True if a job was processed, False if no jobs available
        """
        job = self.dequeue()
        if not job:
            return False

        try:
            handler(job)
            self.complete(job.id)
            return True
        except Exception:
            self.fail(job.id, "Handler exception")
            raise

    def close(self) -> None:
        """Close the queue and release resources."""
        self.queue.close()

    def _record_duration(self, job_id: int) -> None:
        """Record duration for a completed job."""
        start_time = self._job_start_times.pop(job_id, None)
        if start_time:
            import time

            duration = time.perf_counter() - start_time
            self.collector.record_duration(duration)

    def _update_queue_metrics(self) -> None:
        """Update queue-related metrics from underlying queue."""
        try:
            stats = self.queue.get_stats()
            self.collector.set_queue_depth(stats.queue_depth)
            self.collector.update_job_status_count("pending", stats.pending)
            self.collector.update_job_status_count("running", stats.running)
            self.collector.update_job_status_count("completed", stats.completed)
            self.collector.update_job_status_count("failed", stats.failed)
        except Exception:
            # Don't let metrics break the queue
            pass

    def __enter__(self) -> TrackedJobQueue:
        """Context manager entry."""
        return self

    def __exit__(self, *args: Any) -> None:
        """Context manager exit."""
        self.close()

    def __len__(self) -> int:
        """Return total number of jobs in queue."""
        return len(self.queue)
