import std/asyncdispatch
import std/json
import std/options
import std/strutils
import std/times
import ../../libs/sqlite/sqlite_impl
import ../../enums
import ./sqlite_types


proc select*(self:SqliteConnections, columnsArg:varargs[string]):SqliteQuery =
  let query = newJObject()
  
  if columnsArg.len == 0:
    query["select"] = %["*"]
  else:
    query["select"] = %columnsArg
  
  let sqliteQuery = SqliteQuery(
    log: self.log,
    pools: self.pools,
    query: query,
    queryString: "",
    placeHolder: newJArray(),
    isInTransaction: self.isInTransaction,
    transactionConn: self.transactionConn,
  )
  return sqliteQuery


proc table*(self:SqliteConnections, tableArg: string): SqliteQuery =
  let query = newJObject()
  query["table"] = %tableArg

  let sqliteQuery = SqliteQuery(
    log: self.log,
    pools: self.pools,
    query: query,
    queryString: "",
    placeHolder: newJArray(),
    isInTransaction: self.isInTransaction,
    transactionConn: self.transactionConn,
  )
  return sqliteQuery


proc table*(self:SqliteQuery, tableArg: string): SqliteQuery =
  self.query["table"] = %tableArg
  return self


proc `distinct`*(self: SqliteQuery): SqliteQuery =
  self.query["distinct"] = %true
  return self

# ============================== Conditions ==============================

proc join*(self: SqliteQuery, table: string, column1: string, symbol: string,
            column2: string): SqliteQuery =
  if self.query.hasKey("join") == false:
    self.query["join"] = %*[{
      "table": table,
      "column1": column1,
      "symbol": symbol,
      "column2": column2
    }]
  else:
    self.query["join"].add(%*{
      "table": table,
      "column1": column1,
      "symbol": symbol,
      "column2": column2
    })
  return self


proc leftJoin*(self: SqliteQuery, table: string, column1: string, symbol: string,
              column2: string): SqliteQuery =
  if self.query.hasKey("left_join") == false:
    self.query["left_join"] = %*[{
      "table": table,
      "column1": column1,
      "symbol": symbol,
      "column2": column2
    }]
  else:
    self.query["left_join"].add(%*{
      "table": table,
      "column1": column1,
      "symbol": symbol,
      "column2": column2
    })
  return self


const whereSymbols = ["is", "is not", "=", "!=", "<", "<=", ">=", ">", "<>", "LIKE","%LIKE","LIKE%","%LIKE%"]
const whereSymbolsError = """Arg position 3 is only allowed of ["is", "is not", "=", "!=", "<", "<=", ">=", ">", "<>", "LIKE","%LIKE","LIKE%","%LIKE%"]"""

proc where*(self: SqliteQuery, column: string, symbol: string,
            value: string|int|float): SqliteQuery =
  if not whereSymbols.contains(symbol):
    raise newException(
      CatchableError,
      whereSymbolsError
    )

  self.placeHolder.add(%*{"key": column, "value": value})

  if self.query.hasKey("where") == false:
    self.query["where"] = %*[{
      "column": column,
      "symbol": symbol,
      "value": "?"
    }]
  else:
    self.query["where"].add(
      %*{
        "column": column,
        "symbol": symbol,
        "value": "?"
      }
    )
  return self


proc where*(self: SqliteQuery, column: string, symbol: string,
            value: bool): SqliteQuery =
  if not whereSymbols.contains(symbol):
    raise newException(
      CatchableError,
      whereSymbolsError
    )

  self.placeHolder.add(%*{"key":column, "value":value})

  if self.query.hasKey("where") == false:
    self.query["where"] = %*[{
      "column": column,
      "symbol": symbol,
      "value": "?"
    }]
  else:
    self.query["where"].add(
      %*{
        "column": column,
        "symbol": symbol,
        "value": "?"
      }
    )
  return self


proc where*(self: SqliteQuery, column: string, symbol: string, value: nil.type): SqliteQuery =
  if not whereSymbols.contains(symbol):
    raise newException(
      CatchableError,
      whereSymbolsError
    )

  if self.query.hasKey("where") == false:
    self.query["where"] = %*[{
      "column": column,
      "symbol": symbol,
      "value": "null"
    }]
  else:
    self.query["where"].add(
      %*{
        "column": column,
        "symbol": symbol,
        "value": "null"
      }
    )
  return self


proc orWhere*(self: SqliteQuery, column: string, symbol: string,
              value: string|int|float|bool): SqliteQuery =
  if not whereSymbols.contains(symbol):
    raise newException(
      CatchableError,
      whereSymbolsError
    )

  self.placeHolder.add(%*{"key":column, "value":value})

  if self.query.hasKey("or_where") == false:
    self.query["or_where"] = %*[{
      "column": column,
      "symbol": symbol,
      "value": "?"
    }]
  else:
    self.query["or_where"].add(
      %*{
        "column": column,
        "symbol": symbol,
        "value": "?"
      }
    )
  return self

proc orWhere*(self: SqliteQuery, column: string, symbol: string, value: nil.type): SqliteQuery =
  if not whereSymbols.contains(symbol):
    raise newException(
      CatchableError,
      whereSymbolsError
    )

  if self.query.hasKey("or_where") == false:
    self.query["or_where"] = %*[{
      "column": column,
      "symbol": symbol,
      "value": "null"
    }]
  else:
    self.query["or_where"].add(%*{
      "column": column,
      "symbol": symbol,
      "value": "null"
    })
  return self

proc whereBetween*(self:SqliteQuery, column:string, width:array[2, int|float]): SqliteQuery =
  if self.query.hasKey("where_between") == false:
    self.query["where_between"] = %*[{
      "column": column,
      "width": width
    }]
  else:
    self.query["where_between"].add(%*{
      "column": column,
      "width": width
    })
  return self

proc whereBetween*(self:SqliteQuery, column:string, width:array[2, string]): SqliteQuery =
  if self.query.hasKey("where_between_string") == false:
    self.query["where_between_string"] = %*[{
      "column": column,
      "width": width
    }]
  else:
    self.query["where_between_string"].add(%*{
      "column": column,
      "width": width
    })
  return self

proc whereNotBetween*(self:SqliteQuery, column:string, width:array[2, int|float]): SqliteQuery =
  if self.query.hasKey("where_not_between") == false:
    self.query["where_not_between"] = %*[{
      "column": column,
      "width": width
    }]
  else:
    self.query["where_not_between"].add(%*{
      "column": column,
      "width": width
    })
  return self

proc whereNotBetween*(self:SqliteQuery, column:string, width:array[2, string]): SqliteQuery =
  if self.query.hasKey("where_not_between_string") == false:
    self.query["where_not_between_string"] = %*[{
      "column": column,
      "width": width
    }]
  else:
    self.query["where_not_between_string"].add(%*{
      "column": column,
      "width": width
    })
  return self

proc whereIn*(self:SqliteQuery, column:string, width:seq[int|float|string]): SqliteQuery =
  if self.query.hasKey("where_in") == false:
    self.query["where_in"] = %*[{
      "column": column,
      "width": width
    }]
  else:
    self.query["where_in"].add(%*{
      "column": column,
      "width": width
    })
  return self


proc whereNotIn*(self:SqliteQuery, column:string, width:seq[int|float|string]): SqliteQuery =
  if self.query.hasKey("where_not_in") == false:
    self.query["where_not_in"] = %*[{
      "column": column,
      "width": width
    }]
  else:
    self.query["where_not_in"].add(%*{
      "column": column,
      "width": width
    })
  return self


proc whereNull*(self:SqliteQuery, column:string): SqliteQuery =
  if self.query.hasKey("where_null") == false:
    self.query["where_null"] = %*[{
      "column": column
    }]
  else:
    self.query["where_null"].add(%*{
      "column": column
    })
  return self


proc groupBy*(self:SqliteQuery, column:string): SqliteQuery =
  if self.query.hasKey("group_by") == false:
    self.query["group_by"] = %*[{"column": column}]
  else:
    self.query["group_by"].add(%*{"column": column})
  return self


proc having*(self: SqliteQuery, column: string, symbol: string,
              value: string|int|float|bool): SqliteQuery =
  if not whereSymbols.contains(symbol):
    raise newException(
      CatchableError,
      whereSymbolsError
    )

  self.placeHolder.add(%*{"key":column, "value":value})

  if self.query.hasKey("having") == false:
    self.query["having"] = %*[{
      "column": column,
      "symbol": symbol,
      "value": "?"
    }]
  else:
    self.query["having"].add(
      %*{
        "column": column,
        "symbol": symbol,
        "value": "?"
      }
    )
  return self

proc having*(self: SqliteQuery, column: string, symbol: string, value: nil.type): SqliteQuery =
  if not whereSymbols.contains(symbol):
    raise newException(
      CatchableError,
      whereSymbolsError
    )

  if self.query.hasKey("having") == false:
    self.query["having"] = %*[{
      "column": column,
      "symbol": symbol,
      "value": "null"
    }]
  else:
    self.query["having"].add(
      %*{
        "column": column,
        "symbol": symbol,
        "value": "null"
      }
    )
  return self


proc orderBy*(self:SqliteQuery, column:string, order:Order): SqliteQuery =
  if self.query.hasKey("order_by") == false:
    self.query["order_by"] = %*[{
      "column": column,
      "order": $order
    }]
  else:
    self.query["order_by"].add(%*{
      "column": column,
      "order": $order
    })
  return self


proc limit*(self: SqliteQuery, num: int): SqliteQuery =
  self.query["limit"] = %num
  return self


proc offset*(self: SqliteQuery, num: int): SqliteQuery =
  self.query["offset"] = %num
  return self


proc raw*(self:SqliteConnections, sql:string, arges=newJArray()): RawSqliteQuery =
  ## arges is `JArray`
  ## 
  ## can't use BLOB data.
  let rawQueryRdb = RawSqliteQuery(
    log: self.log,
    pools: self.pools,
    query: newJObject(),
    queryString: sql,
    placeHolder: arges,
    isInTransaction: false,
    transactionConn: 0
  )
  return rawQueryRdb
