use super::Options;
use super::{create, delete, find, find_belongs_tos, find_has_manys, find_many, schema, update};
use crate::storage::associations::{BelongsTo, HasMany};
use crate::storage::config_store::adapters::postgres::schema::Table;
use crate::storage::config_store::config_store::FindOptions;
use crate::storage::{Error, config_store::ConfigStore};
use async_trait::async_trait;
use change_case::snake_case;
use pbbson::Model;
use pbbson::bson::{self, Bson, DateTime};
use sqlx::{Pool, Postgres};
use std::collections::HashMap;
use std::time::SystemTime;
use url::Url;

#[allow(unused)]
pub struct PostgresConfigStore<B: Clone + Send + Sync + ToString> {
    belongs_tos_by_bucket: HashMap<String, Vec<BelongsTo<B>>>,
    has_manys_by_bucket: HashMap<String, Vec<HasMany<B>>>,
    pool: Pool<Postgres>,
    tables_by_name: HashMap<String, Table>,
}

impl<B: Clone + Send + Sync + ToString> PostgresConfigStore<B> {
    #[allow(unused)]
    pub async fn new(url: &Url, options: Options) -> Result<Self, Error> {
        let pool = options
            .pg_pool_options
            .connect(url.as_str())
            .await
            .map_err(|e| Error::internal(e.to_string()))?;
        let belongs_tos_by_bucket = find_belongs_tos::find_belongs_tos_by_bucket(&pool).await?;
        let has_manys_by_bucket = find_has_manys::find_has_manys_by_bucket(&pool).await?;
        let tables_by_name = schema::load_schema(&pool).await?;
        if let Some(migrator) = options.migrator {
            migrator.run(&pool).await?;
        }
        Ok(Self {
            belongs_tos_by_bucket,
            has_manys_by_bucket,
            pool,
            tables_by_name,
        })
    }
}

#[async_trait]
impl<B: Clone + Send + Sync + ToString> ConfigStore<B> for PostgresConfigStore<B> {
    async fn create(&self, bucket: B, model: Model) -> Result<Model, Error> {
        let table_name = snake_case(&bucket.to_string());
        let table = match self.tables_by_name.get(&table_name) {
            Some(table) => table,
            None => return Err(Error::NotFound(format!("No such table: {table_name}"))),
        };

        // Set some defaults
        let now = DateTime::from_system_time(SystemTime::now());
        let mut model = model.clone();
        model.set_datetime("createdAt", now);
        if !model.contains_field("id") {
            let id = bson::uuid::Uuid::new();
            model.insert("id", Bson::Binary(id.into()));
        }

        // Build and perform query
        create::perform(&self.pool, table, model.clone()).await?;
        Ok(model)
    }

    async fn delete(&self, bucket: B, id: &str, by_account_id: Option<String>) -> Result<(), Error> {
        let table_name = snake_case(&bucket.to_string());
        let table = match self.tables_by_name.get(&table_name) {
            Some(table) => table,
            None => return Err(Error::NotFound(format!("No such table: {table_name}"))),
        };
        delete::perform(&self.pool, table, id, by_account_id).await
    }

    async fn find(&self, bucket: B, id: &str) -> Result<Model, Error> {
        let table_name = snake_case(&bucket.to_string());
        let table = match self.tables_by_name.get(&table_name) {
            Some(table) => table,
            None => return Err(Error::NotFound(format!("No such table: {table_name}"))),
        };
        find::perform(&self.pool, table, id).await
    }

    async fn find_many(&self, bucket: B, options: FindOptions) -> Result<Vec<Model>, Error> {
        let table_name = snake_case(&bucket.to_string());
        let table = match self.tables_by_name.get(&table_name) {
            Some(table) => table,
            None => return Err(Error::NotFound(format!("No such table: {table_name}"))),
        };
        find_many::perform(&self.pool, table, options).await
    }

    async fn update(&self, bucket: B, model: Model) -> Result<Model, Error> {
        let table_name = snake_case(&bucket.to_string());
        let table = match self.tables_by_name.get(&table_name) {
            Some(table) => table,
            None => return Err(Error::NotFound(format!("No such table: {table_name}"))),
        };

        // Set some defaults
        let now = DateTime::from_system_time(SystemTime::now());
        let mut model = model.clone();
        model.set_datetime("updatedAt", now);

        // Build and perform query
        update::perform(&self.pool, table, model.clone()).await?;
        Ok(model)
    }
}
