require "colorize"
require "log"
require "benchmark"

module Clear::SQL::Logger
  class_property colorize : Bool = STDOUT.tty? && STDERR.tty?

  private SQL_KEYWORDS = Set(String).new(%w(
    ADD ALL ALTER ANALYSE ANALYZE AND ANY ARRAY AS ASC ASYMMETRIC
    BEGIN BOTH BY CASE CAST CHECK COLLATE COLUMN COMMIT CONSTRAINT COUNT CREATE CROSS
    CURRENT_DATE CURRENT_ROLE CURRENT_TIME CURRENT_TIMESTAMP
    CURRENT_USER CURSOR DECLARE DEFAULT DELETE DEFERRABLE DESC
    DISTINCT DROP DO ELSE END EXCEPT EXISTS FALSE FETCH FULL FOR FOREIGN FROM GRANT
    GROUP HAVING IF IN INDEX INNER INSERT INITIALLY INTERSECT INTO JOIN LAGGING
    LEADING LIMIT LEFT LOCALTIME LOCALTIMESTAMP NATURAL NEW NOT NULL OFF OFFSET
    OLD ON ONLY OR ORDER OUTER PLACING PRIMARY REFERENCES RELEASE RETURNING
    RIGHT ROLLBACK SAVEPOINT SELECT SESSION_USER SET SOME SYMMETRIC
    TABLE THEN TO TRAILING TRIGGER TRUE UNION UNIQUE UPDATE USER USING VALUES
    WHEN WHERE WINDOW
  ))

  def self.colorize_query(qry : String)
    return qry unless @@colorize

    o = qry.to_s.split(/([a-zA-Z0-9_]+)/).map do |word|
      if SQL_KEYWORDS.includes?(word.upcase)
        word.colorize.bold.blue.to_s
      elsif word =~ /\d+/
        word.colorize.red
      else
        word.colorize.white
      end
    end.join("")
    o.gsub(/(--.*)$/) { |x| x.colorize.dark_gray }
  end

  def self.display_mn_sec(x : Float64) : String
    mn = x.to_i / 60
    sc = x.to_i % 60

    {mn > 9 ? mn : "0#{mn}", sc > 9 ? sc : "0#{sc}"}.join("mn") + "s"
  end

  def self.display_time(x : Float64) : String
    if (x > 60)
      display_mn_sec(x)
    elsif (x > 1)
      ("%.2f" % x) + "s"
    elsif (x > 0.001)
      (1_000 * x).to_i.to_s + "ms"
    else
      (1_000_000 * x).to_i.to_s + "µs"
    end
  end

  # Log a specific query, wait for it to return
  def log_query(sql : String, &block)
    start_time = Time.monotonic

    o = yield
    elapsed_time = Time.monotonic - start_time

    Log.debug {
      "[" + Clear::SQL::Logger.display_time(elapsed_time.to_f).colorize.bold.white.to_s + "] #{SQL::Logger.colorize_query(sql)}"
    }

    o
  rescue e
    raise Clear::SQL::Error.new(
      message: [e.message, "Error caught, last query was:", Clear::SQL::Logger.colorize_query(sql)].compact.join("\n"),
      cause: e
    )
  end
end
