"""
Async Task Queue

Thread-safe queue for background invoice processing.
"""

import logging
import time
from dataclasses import dataclass, field
from datetime import datetime
from pathlib import Path
from queue import Empty, Full, Queue
import threading
from threading import Event, Lock, Thread
from typing import Callable

from backend.web.core.task_interface import TaskRunner, TaskStatus

logger = logging.getLogger(__name__)


@dataclass
class AsyncTask:
    """Task queued for background processing."""

    request_id: str
    api_key: str
    file_path: Path
    filename: str
    created_at: datetime = field(default_factory=datetime.utcnow)
    priority: int = 0  # Lower = higher priority (not implemented yet)


class AsyncTaskQueue(TaskRunner):
    """Thread-safe queue for async invoice processing."""

    def __init__(
        self,
        max_size: int = 100,
        worker_count: int = 1,
    ) -> None:
        self._queue: Queue[AsyncTask] = Queue(maxsize=max_size)
        self._workers: list[Thread] = []
        self._stop_event = Event()
        self._worker_count = worker_count
        self._lock = Lock()
        self._processing: set[str] = set()  # Currently processing request_ids
        self._task_handler: Callable[[AsyncTask], None] | None = None
        self._started = False

    @property
    def name(self) -> str:
        """Unique identifier for this runner."""
        return "async_task_queue"

    @property
    def is_running(self) -> bool:
        """Check if the queue is running."""
        return self._started and not self._stop_event.is_set()

    def get_status(self) -> TaskStatus:
        """Get current status of the queue."""
        with self._lock:
            processing_count = len(self._processing)

        return TaskStatus(
            name=self.name,
            is_running=self.is_running,
            pending_count=self._queue.qsize(),
            processing_count=processing_count,
        )

    def start(self, task_handler: Callable[[AsyncTask], None]) -> None:
        """Start background worker threads."""
        with self._lock:
            if self._started:
                logger.warning("AsyncTaskQueue already started")
                return

            self._task_handler = task_handler
            self._stop_event.clear()

            for i in range(self._worker_count):
                worker = Thread(
                    target=self._worker_loop,
                    name=f"async-worker-{i}",
                    daemon=True,
                )
                worker.start()
                self._workers.append(worker)
                logger.info(f"Started async worker thread: {worker.name}")

            self._started = True
            logger.info(f"AsyncTaskQueue started with {self._worker_count} workers")

    def stop(self, timeout: float | None = None) -> None:
        """Gracefully stop all workers.

        Args:
            timeout: Maximum time to wait for graceful shutdown.
                    If None, uses default of 30 seconds.
        """
        # Minimize lock scope to avoid potential deadlock
        with self._lock:
            if not self._started:
                return

            logger.info("Stopping AsyncTaskQueue...")
            self._stop_event.set()
            workers_to_join = list(self._workers)

        effective_timeout = timeout if timeout is not None else 30.0

        # Wait for workers to finish outside the lock
        for worker in workers_to_join:
            worker.join(timeout=effective_timeout / self._worker_count)
            if worker.is_alive():
                logger.warning(f"Worker {worker.name} did not stop gracefully")

        with self._lock:
            self._workers.clear()
            self._started = False
        logger.info("AsyncTaskQueue stopped")

    def submit(self, task: AsyncTask) -> bool:
        """
        Submit a task to the queue.

        Returns:
            True if task was queued, False if queue is full
        """
        try:
            self._queue.put_nowait(task)
            logger.info(f"Task {task.request_id} queued for processing")
            return True
        except Full:
            logger.warning(f"Queue full, task {task.request_id} rejected")
            return False

    def get_queue_depth(self) -> int:
        """Get current number of tasks in queue."""
        return self._queue.qsize()

    def get_processing_count(self) -> int:
        """Get number of tasks currently being processed."""
        with self._lock:
            return len(self._processing)

    def is_processing(self, request_id: str) -> bool:
        """Check if a specific request is currently being processed."""
        with self._lock:
            return request_id in self._processing

    def _worker_loop(self) -> None:
        """Worker loop that processes tasks from queue."""
        thread_name = threading.current_thread().name
        logger.info(f"Worker {thread_name} started")

        while not self._stop_event.is_set():
            try:
                # Block for up to 1 second waiting for tasks
                task = self._queue.get(timeout=1.0)
            except Empty:
                continue

            try:
                with self._lock:
                    self._processing.add(task.request_id)

                logger.info(
                    f"Worker {thread_name} processing task {task.request_id}"
                )
                start_time = time.time()

                if self._task_handler:
                    self._task_handler(task)

                elapsed = time.time() - start_time
                logger.info(
                    f"Worker {thread_name} completed task {task.request_id} "
                    f"in {elapsed:.2f}s"
                )

            except Exception as e:
                logger.error(
                    f"Worker {thread_name} failed to process task "
                    f"{task.request_id}: {e}",
                    exc_info=True,
                )

            finally:
                with self._lock:
                    self._processing.discard(task.request_id)
                self._queue.task_done()

        logger.info(f"Worker {thread_name} stopped")

    def wait_for_completion(self, timeout: float | None = None) -> bool:
        """
        Wait for all queued tasks to complete.

        Args:
            timeout: Maximum time to wait in seconds

        Returns:
            True if all tasks completed, False if timeout
        """
        try:
            self._queue.join()
            return True
        except Exception:
            return False
