use anyhow::Context;
use async_trait::async_trait;
use aws_credential_types::Credentials;
use aws_sdk_sqs::config::{BehaviorVersion, Region};
use aws_sdk_sqs::operation::receive_message::ReceiveMessageOutput;
use mockall::automock;

/// The [SqsQueue] trait defines a basic interface for interacting with an
/// AWS SQS queue.
///
/// A [`MockSqsQueue`] struct is automatically generated by the [automock]
/// attribute. This struct can be used in unit tests to mock the behavior of
/// the [SqsQueue] trait.
///
/// The [`SqsQueueImpl`] struct is the actual implementation of the trait.
#[automock]
#[async_trait]
pub trait SqsQueue {
    async fn receive_messages(&self, max_messages: i32) -> anyhow::Result<ReceiveMessageOutput>;
    async fn delete_message(&self, receipt_handle: &str) -> anyhow::Result<()>;
}

/// The [`SqsQueueImpl`] struct is the actual implementation of the [`SqsQueue`]
/// trait, which interacts with the real AWS API servers.
#[derive(Debug, Clone)]
pub struct SqsQueueImpl {
    client: aws_sdk_sqs::Client,
    queue_url: String,
}

impl SqsQueueImpl {
    pub fn new(queue_url: impl Into<String>, region: Region, credentials: Credentials) -> Self {
        let config = aws_sdk_sqs::Config::builder()
            .credentials_provider(credentials)
            .region(region)
            .behavior_version(BehaviorVersion::v2026_01_12())
            .build();

        let client = aws_sdk_sqs::Client::from_conf(config);
        let queue_url = queue_url.into();

        SqsQueueImpl { client, queue_url }
    }
}

#[async_trait]
impl SqsQueue for SqsQueueImpl {
    async fn receive_messages(&self, max_messages: i32) -> anyhow::Result<ReceiveMessageOutput> {
        let response = self
            .client
            .receive_message()
            .max_number_of_messages(max_messages)
            .queue_url(&self.queue_url)
            .send()
            .await
            .context("Failed to receive SQS queue message")?;

        Ok(response)
    }

    async fn delete_message(&self, receipt_handle: &str) -> anyhow::Result<()> {
        self.client
            .delete_message()
            .receipt_handle(receipt_handle)
            .queue_url(&self.queue_url)
            .send()
            .await
            .context("Failed to delete SQS queue message")?;

        Ok(())
    }
}

#[async_trait]
impl<T: SqsQueue + Send + Sync + ?Sized> SqsQueue for Box<T> {
    async fn receive_messages(&self, max_messages: i32) -> anyhow::Result<ReceiveMessageOutput> {
        (**self).receive_messages(max_messages).await
    }

    async fn delete_message(&self, receipt_handle: &str) -> anyhow::Result<()> {
        (**self).delete_message(receipt_handle).await
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_constructor() {
        let credentials = Credentials::new(
            "ANOTREAL",
            "notrealrnrELgWzOk3IfjzDKtFBhDby",
            None,
            None,
            "test",
        );

        let queue_url = "https://sqs.us-west-1.amazonaws.com/359172468976/cdn-log-event-queue";
        let region = Region::new("us-west-1");

        // Check that `SqsQueueImpl::new()` does not panic.
        let _queue = SqsQueueImpl::new(queue_url, region, credentials);
    }
}
