use anyhow::{anyhow, Result};
use async_trait::async_trait;
use futures_util::future::try_join_all;
use google_walletobjects1::api::{
    FlightClass, FlightObject, FlightclasMethods, FlightobjectMethods,
};
use std::sync::Arc;

#[async_trait]
pub trait InsertOrUpdate<T: Clone> {
    async fn insert_now(&self, value: &T) -> Result<bool>;
    async fn update_now(&self, value: &T) -> Result<()>;
}

fn is_already_exists(err: &google_walletobjects1::Error) -> bool {
    match err {
        google_walletobjects1::Error::BadRequest(v) => match v.pointer("/error/code") {
            Some(num) => num == 409,
            None => false,
        },
        _ => false,
    }
}

#[async_trait]
impl<S> InsertOrUpdate<FlightClass> for FlightclasMethods<'_, S>
where
    S: tower_service::Service<http::Uri> + Clone + Send + Sync + 'static,
    S::Response: hyper::client::connect::Connection
        + tokio::io::AsyncRead
        + tokio::io::AsyncWrite
        + Send
        + Unpin
        + 'static,
    S::Future: Send + Unpin + 'static,
    S::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
{
    async fn insert_now(&self, value: &FlightClass) -> Result<bool> {
        self.insert(value.clone())
            .doit()
            .await
            .and(Ok(true))
            .or_else(|e| {
                if is_already_exists(&e) {
                    Ok(false)
                } else {
                    Err(anyhow!(e))
                }
            })
    }
    async fn update_now(&self, value: &FlightClass) -> Result<()> {
        self.update(
            value.clone(),
            &value
                .id
                .clone()
                .ok_or_else(|| anyhow!("no id on FlightClass"))?,
        )
        .doit()
        .await?;
        Ok(())
    }
}

#[async_trait]
impl<S> InsertOrUpdate<FlightObject> for FlightobjectMethods<'_, S>
where
    S: tower_service::Service<http::Uri> + Clone + Send + Sync + 'static,
    S::Response: hyper::client::connect::Connection
        + tokio::io::AsyncRead
        + tokio::io::AsyncWrite
        + Send
        + Unpin
        + 'static,
    S::Future: Send + Unpin + 'static,
    S::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
{
    async fn insert_now(&self, value: &FlightObject) -> Result<bool> {
        self.insert(value.clone())
            .doit()
            .await
            .and(Ok(true))
            .or_else(|e| {
                if is_already_exists(&e) {
                    Ok(false)
                } else {
                    Err(anyhow!(e))
                }
            })
    }
    async fn update_now(&self, value: &FlightObject) -> Result<()> {
        self.update(
            value.clone(),
            &value
                .id
                .clone()
                .ok_or_else(|| anyhow!("no id on FlightObject"))?,
        )
        .doit()
        .await?;
        Ok(())
    }
}

pub async fn insert_or_update_now<T: Clone>(into: &dyn InsertOrUpdate<T>, value: &T) -> Result<()> {
    let created: bool = into.insert_now(value).await?;
    if !created {
        into.update_now(value).await?;
    }
    Ok(())
}

pub async fn insert_or_update_now_arc<T: Clone>(
    into: Arc<&dyn InsertOrUpdate<T>>,
    value: &T,
) -> Result<()> {
    insert_or_update_now(*into.as_ref(), value).await?;
    Ok(())
}

pub async fn insert_or_update_many<T: Clone>(
    into: &dyn InsertOrUpdate<T>,
    values: &Vec<T>,
) -> Result<()> {
    let i = Arc::new(into);
    try_join_all(
        values
            .iter()
            .map(|c| insert_or_update_now_arc(i.clone(), c)),
    )
    .await?;
    Ok(())
}