use crate::databasex::repository::Repository;
use crate::databasex::PostgresTransaction;
use crate::httpx::{AppContext, HttpError, HttpTags};
use axum::http::StatusCode;
use sqlx::query::{Query, QueryAs, QueryScalar};
use sqlx::{Database, FromRow, Postgres};

#[async_trait::async_trait]
#[cfg(not(any(feature = "statsd", feature = "prometheus")))]
impl Repository<Postgres> for PostgresTransaction<'_> {
    async fn fetch_one<'a, S, T>(
        &mut self,
        context: &'a AppContext<S>,
        query_name: &'a str,
        query: QueryAs<'a, Postgres, T, <Postgres as Database>::Arguments<'a>>,
        tags: &HttpTags,
    ) -> Result<T, HttpError>
    where
        T: for<'r> FromRow<'r, <Postgres as Database>::Row> + Send + Unpin,
        S: Clone + Send + Sync,
    {
        let result = query
            .fetch_one(&mut *self.transaction)
            .await
            .map_err(|error| {
                HttpError::without_body(
                    StatusCode::INTERNAL_SERVER_ERROR,
                    format!("Failed to execute fetch one for {query_name} with error: {error}"),
                    tags.clone(),
                )
            });

        result
    }

    async fn fetch_optional<'a, S, T>(
        &mut self,
        context: &'a AppContext<S>,
        query_name: &'a str,
        query: QueryAs<'a, Postgres, T, <Postgres as Database>::Arguments<'a>>,
        tags: &HttpTags,
    ) -> Result<Option<T>, HttpError>
    where
        T: for<'r> FromRow<'r, <Postgres as Database>::Row> + Send + Unpin,
        S: Clone + Send + Sync,
    {
        let result = query
            .fetch_optional(&mut *self.transaction)
            .await
            .map_err(|error| {
                HttpError::without_body(
                    StatusCode::INTERNAL_SERVER_ERROR,
                    format!(
                        "Failed to execute fetch optional for {query_name} with error: {error}"
                    ),
                    tags.clone(),
                )
            });

        result
    }

    async fn fetch_all<'a, S, T>(
        &mut self,
        context: &'a AppContext<S>,
        query_name: &'a str,
        query: QueryAs<'a, Postgres, T, <Postgres as Database>::Arguments<'a>>,
        tags: &HttpTags,
    ) -> Result<Vec<T>, HttpError>
    where
        T: for<'r> FromRow<'r, <Postgres as Database>::Row> + Send + Unpin,
        S: Clone + Send + Sync,
    {
        let result = query
            .fetch_all(&mut *self.transaction)
            .await
            .map_err(|error| {
                HttpError::without_body(
                    StatusCode::INTERNAL_SERVER_ERROR,
                    format!("Failed to execute fetch all for {query_name} with error: {error}"),
                    tags.clone(),
                )
            });

        result
    }

    async fn count<'a, S>(
        &mut self,
        context: &'a AppContext<S>,
        query_name: &'a str,
        query: QueryScalar<'a, Postgres, i64, <Postgres as Database>::Arguments<'a>>,
        tags: &HttpTags,
    ) -> Result<u64, HttpError>
    where
        S: Clone + Send + Sync,
    {
        let result = query
            .fetch_one(&mut *self.transaction)
            .await
            .map_err(|error| {
                HttpError::without_body(
                    StatusCode::INTERNAL_SERVER_ERROR,
                    format!("Failed to execute count for {query_name} with error: {error}"),
                    tags.clone(),
                )
            });

        Ok(result? as u64)
    }

    async fn exists<'a, S>(
        &mut self,
        context: &'a AppContext<S>,
        query_name: &'a str,
        query: QueryScalar<'a, Postgres, bool, <Postgres as Database>::Arguments<'a>>,
        tags: &HttpTags,
    ) -> Result<bool, HttpError>
    where
        S: Clone + Send + Sync,
    {
        let result = query
            .fetch_one(&mut *self.transaction)
            .await
            .map_err(|error| {
                HttpError::without_body(
                    StatusCode::INTERNAL_SERVER_ERROR,
                    format!("Failed to execute exists for {query_name} with error: {error}"),
                    tags.clone(),
                )
            });

        result
    }

    async fn execute<'a, S>(
        &mut self,
        context: &'a AppContext<S>,
        query_name: &'a str,
        query: Query<'a, Postgres, <Postgres as Database>::Arguments<'a>>,
        tags: &HttpTags,
    ) -> Result<(), HttpError>
    where
        S: Clone + Send + Sync,
    {
        query.execute(&mut *self.transaction).await.map_err(|error| {
            HttpError::without_body(
                StatusCode::INTERNAL_SERVER_ERROR,
                format!("Failed to execute fetch one for {query_name} with error: {error}"),
                tags.clone(),
            )
        })?;

        Ok(())
    }
}
