use actix_web::{ResponseError, HttpResponse};
use diesel::result::Error as DieselError;
use reqwest::StatusCode;
use serde::{Serialize, Deserialize};
use std::fmt;

#[derive(Serialize, Deserialize)]
pub struct Message {
  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>,
}

#[derive(Serialize, Deserialize)]
pub struct Response<T> {
  pub data: T,
  #[serde(skip_serializing_if = "Option::is_none")]
  pub metadata: Option<Metadata>
}

#[derive(Serialize, Deserialize)]
pub struct Metadata {
  pub total: i32,
  pub limit: i32,
  pub page: i32,
  pub pages: i32
}

#[derive(Debug, Deserialize, Serialize)]
pub struct ServiceError {
    pub status: u16,
    pub message: String,
}

impl ServiceError {
  pub fn new(error_status_code: u16, error_message: String) -> ServiceError {
    ServiceError {
      status: error_status_code,
      message: error_message,
    }
  }
}

impl fmt::Display for ServiceError {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    f.write_str(self.message.as_str())
  }
}

impl From<DieselError> for ServiceError {
  fn from(error: DieselError) -> ServiceError {
    match error {
      DieselError::DatabaseError(kind, err) => {
        match kind {
          diesel::result::DatabaseErrorKind::UniqueViolation => {
            ServiceError::new(409, err.message().to_string())
          },
          _ => ServiceError::new(500, err.message().to_string())
        }
      },
      DieselError::NotFound => {
        ServiceError::new(404, "The record was not found".to_string())
      },
      DieselError::SerializationError(err) => {
        ServiceError::new(422, err.to_string())
      },
      err => ServiceError::new(500, format!("Unknown database error: {}", err)),
    }
  }
}

impl From<reqwest::Error> for ServiceError {
  fn from(error: reqwest::Error) -> ServiceError {
    ServiceError::new(500, format!("Unknown reqwest error: {}", error))
  }
}

impl From<serde_json::Error> for ServiceError {
  fn from(error: serde_json::Error) -> ServiceError {
    ServiceError::new(500, format!("Unknown serde_json error: {}", error))
  }
}

impl From<serenity::Error> for ServiceError {
  fn from(error: serenity::Error) -> ServiceError {
    ServiceError::new(500, format!("Unknown serenity error: {}", error))
  }
}

impl From<argon2::password_hash::Error> for ServiceError {
  fn from(error: argon2::password_hash::Error) -> ServiceError {
    ServiceError::new(500, format!("Unknown argon2 error: {}", error))
  }
}

impl From<jsonwebtoken::errors::Error> for ServiceError {
  fn from(error: jsonwebtoken::errors::Error) -> ServiceError {
    ServiceError::new(500, format!("Unknown jsonwebtoken error: {}", error))
  }
}

impl From<redis::RedisError> for ServiceError {
  fn from(error: redis::RedisError) -> ServiceError {
    ServiceError::new(500, format!("Unknown redis error: {}", error))
  }
}

impl From<s3::error::S3Error> for ServiceError {
  fn from(error: s3::error::S3Error) -> ServiceError {
    match error {
      s3::error::S3Error::Http(code, message) => {
        ServiceError::new(code, message)
      },
      _ => ServiceError::new(500, format!("Unknown s3 error: {}", error))
    }
  }
}

impl From<s3::creds::error::CredentialsError> for ServiceError {
  fn from(error: s3::creds::error::CredentialsError) -> ServiceError {
    ServiceError::new(500, format!("Unknown credentials error: {}", error))
  }
}

impl ResponseError for ServiceError {
  fn error_response(&self) -> HttpResponse {
      let status_code = match StatusCode::from_u16(self.status) {
          Ok(status_code) => status_code,
          Err(_) => StatusCode::INTERNAL_SERVER_ERROR,
      };

      let error_message = match status_code.as_u16() < 500 {
          true => self.message.clone(),
          false => "Internal server error".to_string(),
      };

      HttpResponse::build(status_code).json(serde_json::json!({ "status": status_code.as_u16(), "message": error_message }))
  }
}
