use diesel::prelude::*;
use serde::{Deserialize, Serialize};
use siren::ServiceError;

use crate::storage::{schema::messages::{self}, connection};

#[derive(Queryable, Selectable, Serialize, Deserialize)]
#[diesel(table_name = messages)]
pub struct QueryMessage {
  pub id: String,
  pub guild_id: i64,
  pub channel_id: i64,
  pub user_id: i64,
  pub created: i64,
  pub model: String,
  pub request: String,
  pub response: String,
  pub request_tags: Vec<String>,
  pub response_tags: Vec<String>,
}

pub struct QueryFilters {
  pub by_id: Option<String>,
  pub by_guild_id: Option<i64>,
  pub by_channel_id: Option<i64>,
  pub by_user_id: Option<i64>,
  pub by_model: Option<String>,
  pub by_request: Option<String>,
  pub by_response: Option<String>,
  pub by_request_tags: Option<Vec<String>>,
  pub by_response_tags: Option<Vec<String>>
}

impl Default for QueryFilters {
  fn default() -> Self {
    QueryFilters {
      by_id: None,
      by_guild_id: None,
      by_channel_id: None,
      by_user_id: None,
      by_model: None,
      by_request: None,
      by_response: None,
      by_request_tags: None,
      by_response_tags: None
    }
  }
}

impl QueryMessage {
  pub fn get_all(filters: &QueryFilters, limit: i32, page: i32) -> Result<Vec<Self>, ServiceError> {
    let mut conn = connection()?;
    let mut query = messages::table.limit(limit as i64).order(messages::created.asc()).into_boxed();
    // Limit query to page and limit
    let offset = (page - 1) * limit;
    query = query.offset(offset as i64);
    // Apply filters
    if let Some(id) = &filters.by_id {
      query = query.filter(messages::id.eq(id));
    }
    if let Some(guild_id) = &filters.by_guild_id {
      query = query.filter(messages::guild_id.eq(guild_id));
    }
    if let Some(channel_id) = &filters.by_channel_id {
      query = query.filter(messages::channel_id.eq(channel_id));
    }
    if let Some(user_id) = &filters.by_user_id {
      query = query.filter(messages::user_id.eq(user_id));
    }
    if let Some(model) = &filters.by_model {
      query = query.filter(messages::model.eq(model));
    }
    if let Some(request) = &filters.by_request {
      query = query.filter(messages::request.eq(request));
    }
    if let Some(response) = &filters.by_response {
      query = query.filter(messages::response.eq(response));
    }
    if let Some(request_tags) = &filters.by_request_tags {
      query = query.filter(messages::request_tags.eq(request_tags));
    }
    if let Some(response_tags) = &filters.by_response_tags {
      query = query.filter(messages::response_tags.eq(response_tags));
    }
    // Execute query
    let messages = query.load::<Self>(&mut conn)?;
    Ok(messages)
  }

  pub fn get_count(fitlers: &QueryFilters) -> Result<i64, ServiceError> {
    let mut conn = connection()?;
    let mut query = messages::table.into_boxed();
    // Apply filters
    if let Some(id) = &fitlers.by_id {
      query = query.filter(messages::id.eq(id));
    }
    if let Some(guild_id) = &fitlers.by_guild_id {
      query = query.filter(messages::guild_id.eq(guild_id));
    }
    if let Some(channel_id) = &fitlers.by_channel_id {
      query = query.filter(messages::channel_id.eq(channel_id));
    }
    if let Some(user_id) = &fitlers.by_user_id {
      query = query.filter(messages::user_id.eq(user_id));
    }
    if let Some(model) = &fitlers.by_model {
      query = query.filter(messages::model.eq(model));
    }
    if let Some(request) = &fitlers.by_request {
      query = query.filter(messages::request.eq(request));
    }
    if let Some(response) = &fitlers.by_response {
      query = query.filter(messages::response.eq(response));
    }
    if let Some(request_tags) = &fitlers.by_request_tags {
      query = query.filter(messages::request_tags.eq(request_tags));
    }
    if let Some(response_tags) = &fitlers.by_response_tags {
      query = query.filter(messages::response_tags.eq(response_tags));
    }
    // Execute query
    let count = query.count().get_result::<i64>(&mut conn)?;
    Ok(count)
  }
}

#[derive(Insertable, AsChangeset, Serialize, Deserialize)]
#[diesel(table_name = messages)]
pub struct InsertMessage {
  pub id: String,
  pub guild_id: i64,
  pub channel_id: i64,
  pub user_id: i64,
  pub created: i64,
  pub model: String,
  pub request: String,
  pub response: String,
  pub request_tags: Vec<String>,
  pub response_tags: Vec<String>,
}

impl InsertMessage {
  pub fn insert(message: Self) -> Result<QueryMessage, ServiceError> {
    let mut conn = connection()?;
    let message = diesel::insert_into(messages::table)
      .values(message)
      .get_result(&mut conn)?;
    Ok(message)
  }
}