"""TaskProcessor - Handles individual task execution with circuit breaker and retry logic."""

import logging
import time
import threading
from concurrent.futures import TimeoutError as FutureTimeout
from queue import Empty
from typing import Callable, Optional, Tuple, Any


class TaskProcessor:
    """Processes individual tasks from queue with fault tolerance and retry."""
    
    def __init__(
        self,
        circuit_breaker,
        retry_strategy,
        metrics_collector,
        auditor,
        executor=None,
        max_retries: int = 3,
    ):
        """Initialize task processor.
        
        Args:
            circuit_breaker: CircuitBreaker instance for fault tolerance
            retry_strategy: RetryStrategy instance for retry decisions
            metrics_collector: Metrics collector for performance tracking
            auditor: Auditor for task completion logging
            executor: ThreadPoolExecutor for async requeue (optional)
            max_retries: Maximum retry attempts
        """
        self.circuit_breaker = circuit_breaker
        self.retry_strategy = retry_strategy
        self.metrics_collector = metrics_collector
        self.auditor = auditor
        self.executor = executor
        self.max_retries = max_retries
    
    def check_ttl(
        self,
        t_arrival: float,
        ttl: float,
        m_id: str,
        corr_id: Optional[str] = None,
    ) -> bool:
        """Check if task has expired by TTL.
        
        Args:
            t_arrival: Task arrival timestamp
            ttl: Time to live in seconds
            m_id: Message ID
            corr_id: Correlation ID for tracing
            
        Returns:
            True if task is still valid, False if expired.
        """
        if (time.time() - t_arrival) > ttl:
            if corr_id:
                logging.debug(f"[TRACE:{corr_id}] Task expired by TTL")
            return False
        return True
    
    def execute_task(
        self,
        m_id: str,
        task: str,
        work_fn: Callable,
        corr_id: Optional[str] = None,
        timeout_seconds: Optional[float] = None,
    ) -> Tuple[bool, Any]:
        """Execute task with circuit breaker protection.
        
        Args:
            m_id: Message ID
            task: Task description
            work_fn: Callable that performs the work
            corr_id: Correlation ID for tracing
            
        Returns:
            Tuple of (success: bool, result: Any)
        """
        if corr_id:
            logging.debug(f"[TRACE:{corr_id}] Starting processing of {m_id}")
        
        def _call_work():
            if self.circuit_breaker:
                return self.circuit_breaker.call(work_fn)
            return work_fn()

        start = time.time()

        try:
            if self.executor and timeout_seconds:
                future = self.executor.submit(_call_work)
                try:
                    result = future.result(timeout=timeout_seconds)
                except FutureTimeout:
                    future.cancel()
                    return False, TimeoutError(
                        f"Task exceeded timeout ({timeout_seconds:.2f}s)"
                    )
            else:
                result = _call_work()

            if timeout_seconds:
                elapsed = time.time() - start
                if elapsed > timeout_seconds:
                    return False, TimeoutError(
                        f"Task exceeded timeout ({elapsed:.2f}s > {timeout_seconds:.2f}s)"
                    )

            return True, result
        except Exception as e:
            if corr_id:
                logging.error(f"[TRACE:{corr_id}] Task execution failed: {str(e)}")
            return False, e
    
    def should_retry(self, error: Exception, attempts: int) -> bool:
        """Determine if task should be retried.
        
        Args:
            error: Exception that occurred
            attempts: Number of attempts made so far
            
        Returns:
            True if should retry, False if should fail permanently.
        """
        return self.retry_strategy.should_retry(error, attempts, self.max_retries)
    
    def get_retry_delay(self, attempts: int) -> float:
        """Get delay before next retry.
        
        Args:
            attempts: Number of attempts made so far
            
        Returns:
            Delay in seconds before retry.
        """
        return self.retry_strategy.get_delay(attempts, self.max_retries)
    
    def requeue_task(
        self,
        inbox,
        priority: int,
        t_arrival: float,
        ttl: float,
        complexity: int,
        attempts: int,
        m_id: str,
        task: str,
        proto: str,
        corr_id: str,
        delay: float,
    ) -> None:
        """Requeue task after delay with incremented attempt counter.
        
        Args:
            inbox: PriorityQueue to requeue task into
            priority: Task priority
            t_arrival: Original arrival timestamp
            ttl: Time to live
            complexity: Task complexity
            attempts: Current attempt number
            m_id: Message ID
            task: Task description
            proto: Protocol
            corr_id: Correlation ID
            delay: Delay before requeue in seconds
        """
        def _requeue_delayed():
            time.sleep(delay)
            inbox.put(
                (priority, t_arrival, ttl, complexity, attempts + 1, m_id, task, proto, corr_id)
            )
        
        if self.executor:
            self.executor.submit(_requeue_delayed)
        else:
            threading.Thread(target=_requeue_delayed, daemon=True).start()
    
    def record_success(
        self,
        m_id: str,
        t_start: float,
        corr_id: Optional[str] = None,
    ) -> float:
        """Record successful task completion.
        
        Args:
            m_id: Message ID
            t_start: Task start timestamp
            corr_id: Correlation ID for tracing
            
        Returns:
            Latency in seconds.
        """
        latency = time.time() - t_start
        self.metrics_collector.registrar_fin_tarea(m_id, exitosa=True)
        if self.auditor:
            self.auditor.registrar_tarea_completada(m_id, latency)
        
        if corr_id:
            logging.debug(f"[TRACE:{corr_id}] Task {m_id} completed successfully")
        
        return latency
    
    def record_failure(
        self,
        m_id: str,
        error: Exception,
        corr_id: Optional[str] = None,
    ) -> None:
        """Record failed task completion.
        
        Args:
            m_id: Message ID
            error: Exception that occurred
            corr_id: Correlation ID for tracing
        """
        self.metrics_collector.registrar_fin_tarea(m_id, exitosa=False)
        if self.auditor:
            self.auditor.registrar_error(m_id, type(error).__name__, str(error)[:100])
        
        if corr_id:
            logging.error(f"[TRACE:{corr_id}] Final failure in {m_id}: {str(error)}")
    
    def record_ttl_expiration(self, m_id: str) -> None:
        """Record task expiration by TTL."""
        self.metrics_collector.registrar_fin_tarea(m_id, exitosa=False)
        logging.warning(f"Task {m_id} expired by TTL")
