use std::error::Error;

use crate::config::load_config;
use diesel::backend::{Backend, RawValue};
use diesel::deserialize::FromSql;
use diesel::r2d2;
use diesel::r2d2::{ConnectionManager, Pool, PooledConnection};
use diesel::serialize::{IsNull, Output, ToSql};
use diesel::sql_types::Text;
use diesel::sqlite::Sqlite;
use diesel::SqliteConnection;
use diesel::{deserialize, serialize};
use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness};

pub fn get_connection_pool() -> Pool<ConnectionManager<SqliteConnection>> {
    let cfg = load_config();
    let url = cfg.database_url();

    let manager = ConnectionManager::<SqliteConnection>::new(url);
    r2d2::Pool::new(manager).unwrap()
}

pub fn establish_connection() -> PooledConnection<ConnectionManager<SqliteConnection>> {
    let pool = get_connection_pool();

    pool.get().unwrap()
}

pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations");

pub fn run_migrations(
    connection: &mut impl MigrationHarness<diesel::sqlite::Sqlite>,
) -> Result<(), Box<dyn Error + Send + Sync + 'static>> {
    connection.run_pending_migrations(MIGRATIONS)?;

    Ok(())
}

#[derive(Debug, AsExpression, FromSqlRow, Clone, PartialEq, Eq)]
#[diesel(sql_type = Text)]
pub struct MyJsonType(pub serde_json::Value);

impl MyJsonType {
    pub fn values(&self) -> Vec<String> {
        if !self.0.is_array() {
            return vec![];
        }

        let array = self.0.as_array().unwrap();
        let mut vec: Vec<String> = vec![];

        for item in array {
            if !item.is_string() {
                continue;
            }

            vec.push(item.as_str().unwrap().trim_matches('"').to_string());
        }
        vec
    }
}

impl<DB> FromSql<Text, DB> for MyJsonType
where
    DB: Backend,
    *const str: FromSql<Text, DB>,
{
    fn from_sql(bytes: RawValue<DB>) -> deserialize::Result<Self> {
        let t = <String as FromSql<Text, DB>>::from_sql(bytes)?;
        Ok(Self(serde_json::from_str(&t)?))
    }
}

impl ToSql<Text, Sqlite> for MyJsonType
where
    String: ToSql<Text, Sqlite>,
{
    fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Sqlite>) -> serialize::Result {
        let s = serde_json::to_string(&self.0)?;

        out.set_value(s);
        Ok(IsNull::No)
    }
}
