use async_trait::async_trait;

use crate::Result;
#[cfg(any(feature = "postgres", feature = "quack"))]
use crate::catalog_queries::{CatalogParam, CatalogQuery};
use crate::embedded_reads::ReadQuery;
use crate::embedded_rows::EmbeddedRow;

pub(crate) enum ReadQueryRender {
    Embedded,
    #[cfg(feature = "postgres")]
    Postgres,
    #[cfg(feature = "quack")]
    Quack,
}

#[cfg(any(feature = "postgres", feature = "quack"))]
pub(crate) enum CatalogQueryRender {
    #[cfg(feature = "postgres")]
    Postgres,
    #[cfg(feature = "quack")]
    Quack,
}

#[async_trait]
pub(crate) trait AsyncReadQueryExecutor {
    fn read_query_render(&self) -> ReadQueryRender;

    #[cfg(feature = "postgres")]
    fn read_query_metadata_schema(&self) -> Option<&str> {
        None
    }

    #[cfg(feature = "quack")]
    fn read_query_metadata_catalog(&self) -> Option<&str> {
        None
    }

    fn render_read_query<T>(&self, query: &ReadQuery<T>) -> String {
        match self.read_query_render() {
            ReadQueryRender::Embedded => query.render_embedded(),
            #[cfg(feature = "postgres")]
            ReadQueryRender::Postgres => query.render_postgres(
                self.read_query_metadata_schema()
                    .expect("Postgres read query rendering requires a metadata schema"),
            ),
            #[cfg(feature = "quack")]
            ReadQueryRender::Quack => {
                query.render_quack_with_metadata_catalog(self.read_query_metadata_catalog())
            }
        }
    }

    async fn execute_rendered_read_query<T>(
        &self,
        sql: String,
        params: Vec<i64>,
        mapper: for<'row> fn(&'row dyn EmbeddedRow) -> Result<T>,
    ) -> Result<Vec<T>>
    where
        T: Send + 'static;

    async fn run_read_query<T>(&self, query: &ReadQuery<T>) -> Result<Vec<T>>
    where
        T: Send + 'static,
    {
        self.execute_rendered_read_query(
            self.render_read_query(query),
            query.params.clone(),
            query.map,
        )
        .await
    }
}

#[cfg(any(feature = "postgres", feature = "quack"))]
#[async_trait]
pub(crate) trait AsyncCatalogQueryExecutor {
    fn catalog_query_render(&self) -> CatalogQueryRender;

    #[cfg(feature = "postgres")]
    fn catalog_query_metadata_schema(&self) -> Option<&str> {
        None
    }

    #[cfg(feature = "quack")]
    fn catalog_query_metadata_catalog(&self) -> Option<&str> {
        None
    }

    fn render_catalog_query(&self, query: &CatalogQuery) -> String {
        match self.catalog_query_render() {
            #[cfg(feature = "postgres")]
            CatalogQueryRender::Postgres => query.render_postgres(
                self.catalog_query_metadata_schema()
                    .expect("Postgres catalog query rendering requires a metadata schema"),
            ),
            #[cfg(feature = "quack")]
            CatalogQueryRender::Quack => {
                query.render_quack_with_metadata_catalog(self.catalog_query_metadata_catalog())
            }
        }
    }

    async fn execute_rendered_catalog_query<T>(
        &self,
        sql: String,
        params: Vec<CatalogParam>,
        mapper: for<'row> fn(&'row dyn EmbeddedRow) -> Result<T>,
    ) -> Result<Vec<T>>
    where
        T: Send;

    async fn execute_rendered_catalog_command(
        &self,
        sql: String,
        params: Vec<CatalogParam>,
    ) -> Result<usize>;

    async fn run_catalog_query<T>(
        &self,
        query: &CatalogQuery,
        mapper: for<'row> fn(&'row dyn EmbeddedRow) -> Result<T>,
    ) -> Result<Vec<T>>
    where
        T: Send,
    {
        self.execute_rendered_catalog_query(
            self.render_catalog_query(query),
            query.params().to_vec(),
            mapper,
        )
        .await
    }

    async fn execute_catalog_command(&self, query: &CatalogQuery) -> Result<usize> {
        self.execute_rendered_catalog_command(
            self.render_catalog_query(query),
            query.params().to_vec(),
        )
        .await
    }
}
