use crate::{database::PsqlData, errors::DatabaseError};
use futures_util::TryStreamExt;
use rand::RngCore;
use rand_chacha::ChaCha8Rng;
use serde::{Deserialize, Serialize};
use sqlx::postgres::PgPool;
use std::{
    error::Error,
    sync::{Arc, Mutex},
};

pub type Random = Arc<Mutex<ChaCha8Rng>>;

#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
pub struct Session {
    pub session_token: Vec<u8>,
    pub user_id: i32,
}

impl Session {
    pub async fn read_by_token(
        pool: &PgPool,
        session_token: &String,
    ) -> Result<Box<Self>, Box<dyn Error>> {
        let token: Vec<u8> = session_token.parse::<u128>()?.to_le_bytes().to_vec();
        let result = sqlx::query_as!(
            Self,
            "SELECT * FROM sessions WHERE session_token = $1",
            token,
        )
        .fetch_one(pool)
        .await?;

        Ok(Box::new(result))
    }
}

impl PsqlData for Session {
    async fn read_all(pool: &PgPool) -> Result<Vec<Box<Self>>, Box<dyn Error>> {
        crate::psql_read_all!(Self, pool, "sessions")
    }

    async fn read(pool: &PgPool, id: i32) -> Result<Box<Self>, Box<dyn Error>> {
        let result = sqlx::query_as!(Self, "SELECT * FROM sessions WHERE user_id = $1", id)
            .fetch_one(pool)
            .await?;

        Ok(Box::new(result))
    }

    async fn insert(&self, pool: &PgPool) -> Result<(), Box<dyn Error>> {
        sqlx::query!(
            "INSERT INTO sessions (session_token, user_id) VALUES ($1, $2)",
            self.session_token,
            self.user_id,
        )
        .execute(pool)
        .await?;

        Ok(())
    }

    async fn update(&self, pool: &PgPool) -> Result<(), Box<dyn Error>> {
        sqlx::query!(
            "UPDATE sessions SET session_token=$1 WHERE user_id=$2",
            self.session_token,
            self.user_id,
        )
        .execute(pool)
        .await?;

        Ok(())
    }

    async fn delete(&self, pool: &PgPool) -> Result<(), Box<dyn Error>> {
        let _result = sqlx::query!("DELETE FROM sessions WHERE user_id = $1", self.user_id)
            .execute(pool)
            .await?;

        Ok(())
    }
}

pub async fn new_session(pool: &PgPool, random: Random, user_id: i32) -> String {
    let mut u128_pool = [0u8; 16];
    random.lock().unwrap().fill_bytes(&mut u128_pool);

    let session_token = u128::from_le_bytes(u128_pool);

    let session = Session {
        user_id,
        session_token: session_token.to_le_bytes().to_vec(),
    };

    session.insert(pool).await.unwrap();

    session_token.to_string()
}

pub async fn clear_sessions_for_user(pool: &PgPool, user_id: i32) -> Result<(), DatabaseError> {
    const QUERY: &str = "DELETE FROM sessions WHERE user_id=$1;";
    let _result = sqlx::query(QUERY)
        .bind(user_id)
        .execute(pool)
        .await
        .unwrap();
    Ok(())
}
