# frozen_string_literal: true

require 'spec_helper'

RSpec.describe 'Database Documentation' do
  context 'for each table' do
    # TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/366834
    let(:database_base_models) { Gitlab::Database.database_base_models.select { |k, _| k != 'geo' } }

    let(:all_tables) do
      database_base_models.flat_map { |_, m| m.connection.tables }.sort.uniq
    end

    let(:metadata_required_fields) do
      %i(
        feature_categories
        table_name
      )
    end

    let(:metadata_allowed_fields) do
      metadata_required_fields + %i(
        classes
        description
        introduced_by_url
        milestone
      )
    end

    let(:metadata) do
      all_tables.each_with_object({}) do |table_name, hash|
        next unless File.exist?(table_metadata_file_path(table_name))

        hash[table_name] ||= load_table_metadata(table_name)
      end
    end

    let(:tables_without_metadata) do
      all_tables.reject { |t| metadata.has_key?(t) }
    end

    let(:tables_without_valid_metadata) do
      metadata.select { |_, t| t.has_key?(:error) }.keys
    end

    let(:tables_with_disallowed_fields) do
      metadata.select { |_, t| t.has_key?(:disallowed_fields) }.keys
    end

    let(:tables_with_missing_required_fields) do
      metadata.select { |_, t| t.has_key?(:missing_required_fields) }.keys
    end

    it 'has a metadata file' do
      expect(tables_without_metadata).to be_empty, multiline_error(
        'Missing metadata files',
        tables_without_metadata.map { |t| "  #{table_metadata_file(t)}" }
      )
    end

    it 'has a valid metadata file' do
      expect(tables_without_valid_metadata).to be_empty, table_metadata_errors(
        'Table metadata files with errors',
        :error,
        tables_without_valid_metadata
      )
    end

    it 'has a valid metadata file with allowed fields' do
      expect(tables_with_disallowed_fields).to be_empty, table_metadata_errors(
        'Table metadata files with disallowed fields',
        :disallowed_fields,
        tables_with_disallowed_fields
      )
    end

    it 'has a valid metadata file without missing fields' do
      expect(tables_with_missing_required_fields).to be_empty, table_metadata_errors(
        'Table metadata files with missing fields',
        :missing_required_fields,
        tables_with_missing_required_fields
      )
    end
  end

  private

  def table_metadata_file(table_name)
    File.join('db', 'docs', "#{table_name}.yml")
  end

  def table_metadata_file_path(table_name)
    Rails.root.join(table_metadata_file(table_name))
  end

  def load_table_metadata(table_name)
    result = {}
    begin
      result[:metadata] = YAML.safe_load(File.read(table_metadata_file_path(table_name))).deep_symbolize_keys

      disallowed_fields = (result[:metadata].keys - metadata_allowed_fields)
      unless disallowed_fields.empty?
        result[:disallowed_fields] = "fields not allowed: #{disallowed_fields.join(', ')}"
      end

      missing_required_fields = (metadata_required_fields - result[:metadata].reject { |_, v| v.blank? }.keys)
      unless missing_required_fields.empty?
        result[:missing_required_fields] = "missing required fields: #{missing_required_fields.join(', ')}"
      end
    rescue Psych::SyntaxError => ex
      result[:error] = ex.message
    end
    result
  end

  def table_metadata_errors(title, field, tables)
    lines = tables.map do |table_name|
      <<~EOM
        #{table_metadata_file(table_name)}
          #{metadata[table_name][field]}
      EOM
    end

    multiline_error(title, lines)
  end

  def multiline_error(title, lines)
    <<~EOM
      #{title}:

      #{lines.join("\n")}
    EOM
  end
end