require "scenic"
require "ext/scenic/adapters/postgres"

module SearchCraft::Model
  # Maintain a list of classes that include this module
  @included_classes = []

  # Class method to add a class to the list of included classes
  def self.included(base)
    if base.is_a?(Class)
      base.extend ClassMethods

      if base.is_a?(ClassMethods) && base.respond_to?(:table_name=)
        base.table_name = base.name.to_s.tableize.tr("/", "_")

        @included_classes << base unless @included_classes.include?(base)
      end
    end
    super
  end

  # Runs .refresh! on all classes that include SearchCraft::Model
  def self.refresh_all!
    if SearchCraft.config.explicit_model_class_names.present?
      SearchCraft.config.explicit_model_class_names.each do |model_class_name|
        model_class_name.constantize.refresh!
      end
    else
      included_classes.each do |klass|
        if klass.is_a?(ClassMethods)
          klass.refresh!
        end
      end
    end
  end

  def self.refresh_any_unpopulated!
    included_classes.each do |klass|
      klass.refresh! unless klass.populated?
    end
  end

  def self.included_classes
    @included_classes | if SearchCraft.config.explicit_model_class_names
      SearchCraft.config.explicit_model_class_names.map(&:constantize)
    else
      []
    end
  end

  module ClassMethods
    def refresh!
      refresh_concurrently = @refresh_concurrently && populated?
      unless Rails.env.test?
        if refresh_concurrently
          warn "Refreshing materialized view concurrently #{table_name}..."
        else
          warn "Refreshing materialized view #{table_name}..."
        end
      end

      Scenic.database.refresh_materialized_view(table_name, concurrently: refresh_concurrently, cascade: false)
    rescue ActiveRecord::StatementInvalid
      # If populated? lies and returns true; then might get error:
      # PG::FeatureNotSupported: ERROR:  CONCURRENTLY cannot be used when the materialized view is not populated (ActiveRecord::StatementInvalid)
      Scenic.database.refresh_materialized_view(table_name, concurrently: false, cascade: false)
    end

    def populated?
      Scenic.database.populated?(table_name)
    end

    def refresh_concurrently=(value)
      @refresh_concurrently = value
    end

    # Checks the database server to see if the materialized view is currently being refreshed
    def currently_refreshing?
      # quoted_table_name is table_name, but with double quotes around each chunk
      # e.g. "schema"."table" or "table"
      quoted_table_name = Scenic.database.quote_table_name(table_name)
      dbname = ActiveRecord::Base.connection_db_config.database
      sql = <<~SQL
        SELECT EXISTS (
          SELECT 1
          FROM pg_stat_activity
          WHERE datname = '#{dbname}'
            AND query LIKE '%REFRESH MATERIALIZED VIEW #{quoted_table_name}%'
            AND pid <> pg_backend_pid()
        ) AS is_refresh_running;
      SQL

      warn "Checking if #{table_name} is currently being refreshed..." if SearchCraft.debug?
      if (result = ActiveRecord::Base.connection.execute(sql))
        result.first["is_refresh_running"]
      else
        false
      end
    end
  end

  def read_only?
    true
  end
end
