use crate::RequestContext;
use dbui_core::{Project, RequestMessage, ResponseMessage, Result, ResultExt};

/// Core application logic, routing [RequestMessage](dbui_core::RequestMessage)s and emitting [ResponseMessage](dbui_core::ResponseMessage)s.
#[derive(Debug)]
pub struct MessageHandler {
  project: Project,
  request: RequestContext,
  log: slog::Logger
}

impl MessageHandler {
  pub fn new(project: Project, request: RequestContext) -> MessageHandler {
    let log = request.log().new(slog::o!("service" => "message_handler"));
    MessageHandler { project, request, log }
  }

  pub fn on_open(&self) -> Vec<ResponseMessage> {
    vec![ResponseMessage::Connected {
      p: Box::new(self.project.to_description()),
      u: Box::new((*self.request.user()).clone()),
      b: !self.request.app().verbose()
    }]
  }

  pub fn on_closed(&self) -> Vec<ResponseMessage> {
    Vec::new()
  }

  pub fn on_message(&self, msg: RequestMessage) -> Result<Vec<ResponseMessage>> {
    let mut ret = Vec::new();
    match msg {
      RequestMessage::Ping { v } => ret.push(ResponseMessage::Pong { v }),
      RequestMessage::RunQuery { id, ctx, sql } => ret.push(self.run_query(id, &ctx, &sql)?),
      RequestMessage::RefreshSchema { full } => ret.push(self.refresh_schema(full)?),
      msg => slog::warn!(self.log, "Unhandled RequestMessage [{:?}]", msg)
    }
    Ok(ret)
  }

  pub fn on_error(&self) {}

  pub fn log(&self) -> &slog::Logger {
    &self.log
  }

  fn run_sql(&self, id: uuid::Uuid, sql: &str) -> Result<Vec<postgres::row::Row>> {
    let mut conn = self.request.app().open_db(self.project.key())?;
    let stmt = conn.prepare(sql).chain_err(|| format!("Cannot parse query [{}]", id))?;
    conn.query(&stmt, &[]).chain_err(|| format!("Cannot run query [{}]", id))
  }

  fn run_query(&self, id: uuid::Uuid, ctx: &str, sql: &str) -> Result<ResponseMessage> {
    slog::info!(self.log(), "Running SQL as [{}/{}]: {}", ctx, id, sql);
    let start_ms = dbui_core::util::current_time_ms();
    let rows = self.run_sql(id, sql)?;
    let duration_ms = (dbui_core::util::current_time_ms() - start_ms) as i32;
    let result = dbui_database::from_rows(id, ctx, sql, rows, duration_ms)?;
    Ok(ResponseMessage::SqlResponse { result })
  }

  fn refresh_schema(&self, full: bool) -> Result<ResponseMessage> {
    let conn = self.request.app().open_db(self.project.key())?;
    dbui_database::schema::refresh::refresh_schema(&self.project, conn, self.log(), full)
  }
}
