require_relative '../../../lib/varnish/lib/cartodb-varnish'
require_relative '../user'
require_relative 'user_table'

module CartoDB
  class TablePrivacyManager

    def initialize(table)
      @table = table
    end

    def apply_privacy_change(metadata_table, old_privacy, privacy_changed = false)
      if @table.table_visualization
        affected_entities = [@table.table_visualization] + metadata_table.affected_visualizations

        @table.map.save

        # Separate on purpose as if fails here no need to revert visualizations' privacy
        revertable_privacy_change(metadata_table, old_privacy) do
          # Map saving actually doesn't changes privacy, but to keep on same reverting logic
          @table.map.save
          set_from_table_privacy(@table.privacy)
        end

        revertable_privacy_change(metadata_table, old_privacy, affected_entities) do
          propagate_to([@table.table_visualization])
          notify_privacy_affected_entities(metadata_table) if privacy_changed
        end
      end
    end

    def set_from_visualization(visualization)
      set_public                if visualization.public?
      set_public_with_link_only if visualization.public_with_link?
      set_private               if visualization.private? || visualization.organization?
      table.update(privacy: privacy)
      self
    end

    def update_cdb_tablemetadata
      table.update_cdb_tablemetadata
    end

    private

    attr_reader :table
    attr_accessor :privacy

    def set_from_table_privacy(table_privacy)
      case table_privacy
      when Carto::UserTable::PRIVACY_PUBLIC
        set_public
      when Carto::UserTable::PRIVACY_LINK
        set_public_with_link_only
      else
        set_private
      end
    end

    # Propagation flow: Table -> Table PrivacyManager -> Visualization -> Visualization NamedMap
    def propagate_to(visualizations, table_privacy_changed = false)
      visualizations.each do |visualization|
        if visualization.type == CartoDB::Visualization::Member::TYPE_CANONICAL
          # Each table has a canonical visualization which must have privacy synced
          visualization.privacy = Carto::UserTable::PRIVACY_VALUES_TO_TEXTS[privacy]
          visualization.store_using_table(table_privacy_changed)
        else
          visualization.invalidate_cache
        end
      end

      self
    end

    def set_public
      self.privacy = Carto::UserTable::PRIVACY_PUBLIC
      Carto::TableAndFriends.apply(owner.in_database, owner.database_schema, table.name) do |schema, table_name|
        set_database_permissions(grant_query(schema, table_name))
      end
      self
    end

    def set_private
      self.privacy = Carto::UserTable::PRIVACY_PRIVATE
      Carto::TableAndFriends.apply(owner.in_database, owner.database_schema, table.name) do |schema, table_name|
        set_database_permissions(revoke_query(schema, table_name))
      end
      self
    end

    def set_public_with_link_only
      self.privacy = Carto::UserTable::PRIVACY_LINK
      Carto::TableAndFriends.apply(owner.in_database, owner.database_schema, table.name) do |schema, table_name|
        set_database_permissions(grant_query(schema, table_name))
      end
      self
    end

    def owner
      @owner ||= ::User.where(id: table.user_id).first
    end

    def set_database_permissions(query)
      owner.in_database(as: :superuser).run(query)
    end

    def revoke_query(schema, table_name)
      %{
        REVOKE SELECT ON #{fully_qualified_table_name(schema, table_name)}
        FROM #{CartoDB::PUBLIC_DB_USER}
      }
    end

    def grant_query(schema, table_name)
      %{
        GRANT SELECT ON #{fully_qualified_table_name(schema, table_name)}
        TO #{CartoDB::PUBLIC_DB_USER};
      }
    end

    def revertable_privacy_change(metadata_table, old_privacy = nil, entities = [])
      yield
    rescue StandardError => exception
      CartoDB.notify_exception(exception, user_id: metadata_table.user_id, table_id: metadata_table.id)
      revert_to_previous_privacy(metadata_table, old_privacy, entities)
      raise exception
    end

    def revert_to_previous_privacy(metadata_table, old_privacy, additional_revert_targets = [])
      return unless old_privacy

      set_from_table_privacy(old_privacy)

      errors = []
      additional_revert_targets.each do |visualization|
        begin
          propagate_to([visualization])
        rescue StandardError => exception
          # Can't do much more, just let remaining ones finish
          errors << exception
        end
      end

      if !errors.empty?
        CartoDB.notify_error("Errors reverting Table privacy", user_id: metadata_table.user_id,
                                                               table_id: metadata_table.id,
                                                               errors: errors)
      end
    end

    def notify_privacy_affected_entities(metadata_table)
      propagate_to(metadata_table.affected_visualizations, true)
      update_cdb_tablemetadata
    end

    def fully_qualified_table_name(schema, table_name)
      %{"#{schema}"."#{table_name}"}
    end
  end
end
