//! Tracked delivery that reports events on ack/nack.

use chrono::{DateTime, Utc};
use queuerious::{Backend, JobEvent, QueuriousClient};
use std::sync::Arc;
use uuid::Uuid;

#[cfg(feature = "metrics")]
use std::collections::HashMap;

#[cfg(feature = "metrics")]
use crate::metrics::SlidingWindowCounters;

/// A delivery from RabbitMQ that automatically reports events on ack/nack/reject.
///
/// Reports `job_completed` on ack, `job_retrying` on nack/reject with requeue,
/// and `job_failed` on nack/reject without requeue.
///
/// When the `metrics` feature is enabled and counters are attached, also records
/// processing duration and success/failure for Tier 0 metrics collection.
pub struct TrackedDelivery {
    inner: lapin::message::Delivery,
    client: Arc<QueuriousClient>,
    queue_name: String,
    execution_id: Uuid,
    created_at: DateTime<Utc>,
    #[cfg(feature = "metrics")]
    started_at: std::time::Instant,
    #[cfg(feature = "metrics")]
    counters: Option<Arc<HashMap<String, SlidingWindowCounters>>>,
}

impl TrackedDelivery {
    pub(crate) fn new(
        delivery: lapin::message::Delivery,
        client: Arc<QueuriousClient>,
        queue_name: String,
        execution_id: Uuid,
        created_at: DateTime<Utc>,
    ) -> Self {
        Self {
            inner: delivery,
            client,
            queue_name,
            execution_id,
            created_at,
            #[cfg(feature = "metrics")]
            started_at: std::time::Instant::now(),
            #[cfg(feature = "metrics")]
            counters: None,
        }
    }

    /// Attach shared counters for Tier 0 metrics recording.
    #[cfg(feature = "metrics")]
    pub(crate) fn with_counters(
        mut self,
        counters: Arc<HashMap<String, SlidingWindowCounters>>,
    ) -> Self {
        self.counters = Some(counters);
        self
    }

    /// Access the underlying lapin delivery.
    pub fn delivery(&self) -> &lapin::message::Delivery {
        &self.inner
    }

    /// Access the message data.
    pub fn data(&self) -> &[u8] {
        &self.inner.data
    }

    /// The delivery tag.
    pub fn delivery_tag(&self) -> u64 {
        self.inner.delivery_tag
    }

    /// The execution ID assigned by the Queuerious server.
    pub fn execution_id(&self) -> Uuid {
        self.execution_id
    }

    /// Acknowledge the delivery and report `job_completed`.
    ///
    /// When the `metrics` feature is enabled, also records processing duration
    /// and success to the shared counters.
    pub async fn ack(&self, options: lapin::options::BasicAckOptions) -> Result<(), lapin::Error> {
        let result = self.inner.ack(options).await;

        if result.is_ok() {
            #[cfg(feature = "metrics")]
            self.record_metrics(true);

            let event = JobEvent::completed(
                &self.queue_name,
                Backend::RabbitMQ,
                self.execution_id,
                self.created_at,
            );
            if let Err(e) = self.client.report(event) {
                tracing::debug!(error = %e, "Failed to report job_completed to Queuerious");
            }
        }

        result
    }

    /// Reject the delivery and report a lifecycle event.
    ///
    /// Reports `job_retrying` if `requeue` is true, `job_failed` otherwise.
    /// When the `metrics` feature is enabled and `requeue` is false, records
    /// a failure to the shared counters.
    pub async fn reject(
        &self,
        options: lapin::options::BasicRejectOptions,
        error_message: &str,
    ) -> Result<(), lapin::Error> {
        let result = self.inner.reject(options).await;

        if result.is_ok() {
            #[cfg(feature = "metrics")]
            if !options.requeue {
                self.record_metrics(false);
            }

            self.report_failure_event(options.requeue, error_message);
        }

        result
    }

    /// Nack the delivery and report a lifecycle event.
    ///
    /// Reports `job_retrying` if `requeue` is true, `job_failed` otherwise.
    /// When the `metrics` feature is enabled and `requeue` is false, records
    /// a failure to the shared counters.
    pub async fn nack(
        &self,
        options: lapin::options::BasicNackOptions,
        error_message: &str,
    ) -> Result<(), lapin::Error> {
        let result = self.inner.nack(options).await;

        if result.is_ok() {
            #[cfg(feature = "metrics")]
            if !options.requeue {
                self.record_metrics(false);
            }

            self.report_failure_event(options.requeue, error_message);
        }

        result
    }

    /// Report a `job_retrying` event without acknowledging or rejecting the delivery.
    ///
    /// Use this when your retry mechanism handles AMQP operations separately,
    /// such as the common "republish-to-retry-queue then ACK via raw channel" pattern.
    pub fn report_retrying(&self, error_message: &str) {
        let event = JobEvent::retrying(
            &self.queue_name,
            Backend::RabbitMQ,
            self.execution_id,
            self.created_at,
            error_message,
        );
        if let Err(e) = self.client.report(event) {
            tracing::debug!(error = %e, "Failed to report job_retrying to Queuerious");
        }
    }

    /// Report a `job_dead_lettered` event without acknowledging or rejecting the delivery.
    ///
    /// Use this when your dead-letter mechanism handles AMQP operations separately,
    /// such as NACKing via the raw channel after max retries are exceeded.
    pub fn report_dead_lettered(&self, error_message: &str) {
        let event = JobEvent::dead_lettered(
            &self.queue_name,
            Backend::RabbitMQ,
            self.execution_id,
            self.created_at,
            error_message,
        );
        if let Err(e) = self.client.report(event) {
            tracing::debug!(error = %e, "Failed to report job_dead_lettered to Queuerious");
        }
    }

    fn report_failure_event(&self, requeue: bool, error_message: &str) {
        let event = if requeue {
            JobEvent::retrying(
                &self.queue_name,
                Backend::RabbitMQ,
                self.execution_id,
                self.created_at,
                error_message,
            )
        } else {
            JobEvent::failed(
                &self.queue_name,
                Backend::RabbitMQ,
                self.execution_id,
                self.created_at,
                error_message,
            )
            .build()
        };

        if let Err(e) = self.client.report(event) {
            tracing::debug!(error = %e, "Failed to report job event to Queuerious");
        }
    }

    /// Record processing duration and outcome to the shared counters.
    #[cfg(feature = "metrics")]
    fn record_metrics(&self, success: bool) {
        if let Some(ref counters) = self.counters {
            if let Some(counter) = counters.get(&self.queue_name) {
                let elapsed_ms = self.started_at.elapsed().as_millis() as u64;
                counter.record_processed(elapsed_ms, success);
            }
        }
    }
}
