use std::sync::Arc;
use tokio::time::{sleep, timeout, Duration};

#[cfg(feature = "tracing")]
use tracing::{debug, error, warn};

use crate::{
    Job, error::JobResult, registry::JOB_REGISTRY
};

use super::JobQueue;

pub struct JobWorker {
    queue: Arc<JobQueue>,
    poll_interval: Duration,
}

impl JobWorker {
    /// Create a new worker
    pub fn new(queue: Arc<JobQueue>) -> Self {
        Self {
            queue,
            poll_interval: Duration::from_millis(100),
        }
    }

    /// Get the poll interval
    #[cfg(test)]
    pub fn poll_interval(&self) -> Duration {
        self.poll_interval
    }

    /// Run the worker loop
    pub async fn run(&self) -> JobResult<()> {
        loop {
            match self.queue.claim_next_job().await {
                Ok(Some(job)) => {
                    let _ = self.process_job(job).await;
                }
                Ok(None) => sleep(self.poll_interval).await,
                Err(_e) => {
                    #[cfg(feature = "tracing")]
                    debug!("Worker failed to claim next job from queue: {:?}", _e);
                    // Wait before retrying to avoid tight error loop
                    sleep(self.poll_interval).await;
                }
            }
        }
    }

    /// Execute a single job
    async fn process_job(&self, job: Job) -> JobResult<()> {
        let Some(handler) = JOB_REGISTRY.get_handler(&job.job_type) else {
            let _job_id = job.id;
            let job_type = job.job_type.clone();

            #[cfg(feature = "tracing")]
            warn!(
                "Worker no handler registered for job {} with type: {}",
                _job_id,
                job_type
            );

            self.fail_job_logged(
                job,
                format!("No handler registered for job type: {}", job_type),
            )
            .await;

            return Ok(());
        };

        let timeout_duration = self.queue.duration().to_std().unwrap_or(Duration::from_secs(300));

        match timeout(timeout_duration, handler(job.clone(), self.queue.clone())).await {
            Ok(Ok(())) => {
                // Job completed successfully
            }
            Ok(Err(e)) => {
                #[cfg(feature = "tracing")]
                error!(
                    "Worker handler failed for job {} (type: {}): {:?}",
                    job.id,
                    job.job_type,
                    e
                );

                self.fail_job_logged(
                    job,
                    format!("Handler error: {:?}", e),
                )
                .await;
            }
            Err(_) => {
                #[cfg(feature = "tracing")]
                error!(
                    "Worker handler timed out for job {} (type: {}) after {:?}",
                    job.id,
                    job.job_type,
                    self.queue.duration()
                );

                self.fail_job_logged(
                    job,
                    format!("Job timed out after {:?}", self.queue.duration()),
                )
                .await;
            }
        }

        Ok(())
    }

    /// Attempt to mark a job as failed and log if that fails
    async fn fail_job_logged(&self, job: Job, reason: String) {
        let _job_id = job.id;
        if let Err(_e) = self.queue.fail_job(job, &reason).await {
            #[cfg(feature = "tracing")]
            error!(
                "Worker failed to mark job {} as failed: {:?}",
                _job_id,
                _e
            );
        }
    }
}