# frozen_string_literal: true

require 'open3'
require 'csv'
require 'erb'
require 'json'

class RspecQuery
  attr_reader :name, :title, :sql, :prepare, :description

  def initialize(opts)
    @name = opts[:name]
    @title = opts[:title] || opts[:name]
    @sql = opts[:query]
    @prepare = opts[:prepare]
    @hidden = opts[:hidden]
    @description = opts[:description]
  end

  def write_csv
    filename = filename(:csv)
    if File.exist?(filename)
      warn "#{filename} already exists, skipping"
      return
    end

    with_time do
      if sql.is_a?(Proc)
        sql.call(self)
      else
        exec_query(filename)
      end
    end
  end

  def write_json
    filename = filename(:json)
    return if csv.empty?

    with_time do
      json = JSON.generate(data: csv.map(&:fields))
      File.write(filename, json)
    end
  end

  def filename(ext)
    "tmp/rspec/#{basename(ext)}"
  end

  def basename(ext)
    "#{name}.#{ext}"
  end

  def csv
    options = {
      headers: true,
      skip_lines: /\(\d+ rows\)/ # Skip lines like "(1000 rows)"
    }

    csv_filename = filename(:csv)
    @csv ||= CSV.read(csv_filename, **options)
  rescue StandardError => e
    warn "warning: failed to parse #{csv_filename}: #{e.message}"
    []
  end

  def query_id
    name.tr(' ', '-')
  end

  def html_table_id
    "#{query_id}_table"
  end

  def html
    return if csv.empty?

    template = File.join(File.dirname(__FILE__), 'templates/table.erb')
    ERB.new(File.read(template)).result(binding)
  end

  def table_headers
    headers = csv.headers
    headers.append('') unless name == 'overall_time'
    headers
  end

  def hidden?
    @hidden == true
  end

  def avg_change_increase
    avg_change do |csv, records|
      top_sorted = records.sort_by { |rec| -rec[1] }.first(1000)
      top_sorted.each { |rec| csv << rec }
    end
  end

  def avg_change_decrease
    avg_change do |csv, records|
      top_sorted = records.sort_by { |rec| rec[1] }.first(1000)
      top_sorted.each { |rec| csv << rec }
    end
  end

  private

  def avg_change
    CSV.open(filename(:csv), 'w', write_headers: true) do |csv|
      csv << %w[file pct_change_impact avg_time runtime_weight pct_change pct_change_by_day]

      yield csv, avg_records
    end
  end

  def avg_records
    return @avg_records if @avg_records

    q = SQL_QUERIES.find { |q| q[:name] == 'avg_time_per_file_per_day' }
    orig_query = RspecQuery.new(q)

    today = Date.today
    days = (today - 7..today - 1).map(&:to_s)
    by_file = orig_query.csv.group_by { |row| row['file'] }
    last_day = days.last

    @avg_records = by_file.filter_map do |file, rows|
      next unless last = rows.find { |r| r['day'] == last_day }

      first = rows.find { |r| r['day'] == days.first }
      diff_pct = diff_percent(first, last).to_f
      weight = last['sum_time'].to_f
      pct_impact = (diff_pct * weight).to_i

      [file, pct_impact, last['avg_time'], weight.to_i, diff_pct, pct_history(days, rows)]
    end
  end

  def pct_history(days, rows)
    last = nil
    days.map do |day|
      next_row = rows.find { |r| r['day'] == day }

      pct = next_row && last ? diff_percent(last, next_row) : 'n/a'
      last = next_row
      pct
    end.drop(1).join(', ')
  end

  def diff_percent(first, last)
    return unless first && last

    ((last['avg_time'].to_f - first['avg_time'].to_f) * 100 / first['avg_time'].to_f).to_i
  end

  def with_time
    stime = Time.now
    yield
    puts "time: #{Time.now - stime}"
  end

  def exec_query(csv_filename)
    if ENV['PGURI'].nil?
      env = ENV.slice('PGHOST', 'PGDATABASE', 'PGUSER', 'PGPASSWORD', 'PGSSLMODE', 'PGSSLCERT', 'PGSSLKEY',
        'PGSSLROOTCERT')
      cmd = ['psql', '-A', '-F', ',', '-o', csv_filename, '-f', '-']
      # cmd = ['psql', '-f', '-']
    else
      env = {}
      cmd = ['psql', ENV['PGURI'], '-A', '-F', ',', '-o', csv_filename, '-f', '-']
      # cmd = ['psql', ENV['PGURI'], '-f', '-']
    end

    puts "creating #{csv_filename}"
    Open3.popen2(env, *cmd) do |stdin, _stdout, thr|
      stdin.puts sql
      stdin.close
      # puts stdout.read
      exit_status = thr.value

      raise "psql failed" unless exit_status.success?
    end
  end
end
