# frozen_string_literal: true

class AccountRelationshipsPresenter
  attr_reader :following, :showing_reblogs, :notifying, :delivery_following, :followed_by, :subscribing, :blocking, :blocked_by,
              :muting, :muting_notifications, :requested, :domain_blocking,
              :endorsed, :account_note

  def initialize(account_ids, current_account_id, **options)
    @account_ids        = account_ids.map { |a| a.is_a?(Account) ? a.id : a.to_i }.uniq
    @current_account_id = current_account_id

    @following            = cached[:following]
    @showing_reblogs      = cached[:showing_reblogs]
    @notifying            = cached[:notifying]
    @delivery_following   = cached[:delivery_following]
    @followed_by          = cached[:followed_by]
    @subscribing          = cached[:subscribing]
    @blocking             = cached[:blocking]
    @blocked_by           = cached[:blocked_by]
    @muting               = cached[:muting]
    @muting_notifications = cached[:muting_notifications]
    @requested            = cached[:requested]
    @domain_blocking      = cached[:domain_blocking]
    @endorsed             = cached[:endorsed]
    @account_note         = cached[:account_note]

    if current_account_id.present? && !account_ids.empty?
      result = ActiveRecord::Base.connection.select_all(ActiveRecord::Base.sanitize_sql_array([<<-SQL.squish, account_ids: @uncached_account_ids, current_account_id: @current_account_id])).to_a.first
      with
        followings as (select * from follows where account_id = :current_account_id and target_account_id in (:account_ids)),
        follow_requesteds as (select * from follow_requests where account_id = :current_account_id and target_account_id in (:account_ids)),
        filter_mutes as (select * from mutes where account_id = :current_account_id and target_account_id in (:account_ids)),
        subscribe_lists as (select target_account_id, coalesce(list_id, -1) as id, json_object_agg('reblogs', show_reblogs) as reblogs from account_subscribes where account_id = :current_account_id and target_account_id in (:account_ids) group by target_account_id, id)
      select
        (select string_agg(target_account_id::text, ',') from followings) as following,
        (select string_agg(target_account_id::text, ',') from (select target_account_id from followings where show_reblogs union all select target_account_id from follow_requesteds where show_reblogs) a) as showing_reblogs,
        (select string_agg(target_account_id::text, ',') from (select target_account_id from followings where notify union all select target_account_id from follow_requesteds where notify) a) as notifying,
        (select string_agg(target_account_id::text, ',') from (select target_account_id from followings where delivery union all select target_account_id from follow_requesteds where delivery) a) as delivery_following,
        (select string_agg(target_account_id::text, ',') from follow_requesteds) as requested,
        (select string_agg(account_id::text, ',') from follows where target_account_id = :current_account_id and account_id in (:account_ids)) as followed_by,
        (select json_object_agg(list.target_account_id, list.val)
          from (select target_account_id, json_object_agg(lists.id, lists.reblogs) as val from subscribe_lists as lists group by target_account_id) as list) as subscribing,
        (select string_agg(target_account_id::text, ',') from blocks where account_id = :current_account_id and target_account_id in (:account_ids)) as blocking,
        (select string_agg(account_id::text, ',') from blocks where target_account_id = :current_account_id and account_id in (:account_ids)) as blocked_by,
        (select string_agg(target_account_id::text, ',') from filter_mutes) as muting,
        (select string_agg(target_account_id::text, ',') from filter_mutes where hide_notifications) as muting_notifications,
        (select string_agg(adb.account_id::text, ',') from accounts a join account_domain_blocks adb on a.domain = adb.domain where adb.account_id = :current_account_id and a.id in (:account_ids)) as domain_blocking,
        (select string_agg(target_account_id::text, ',') from account_pins where account_id = :current_account_id and target_account_id in (:account_ids)) as endorsed,
        (select json_object_agg(n.target_account_id, n.val)
          from (select target_account_id, json_object_agg('comment', comment) as val from account_notes where account_id = :current_account_id and target_account_id in (:account_ids) group by target_account_id) as n) as account_note
      SQL

      @following.merge!(mapping_from_string(result['following']))
      @showing_reblogs.merge!(mapping_from_string(result['showing_reblogs']))
      @notifying.merge!(mapping_from_string(result['notifying']))
      @delivery_following.merge!(mapping_from_string(result['delivery_following']))
      @followed_by.merge!(mapping_from_string(result['followed_by']))
      @subscribing.merge!(mapping_from_json(result['subscribing']))
      @blocking.merge!(mapping_from_string(result['blocking']))
      @blocked_by.merge!(mapping_from_string(result['blocked_by']))
      @muting.merge!(mapping_from_string(result['muting']))
      @muting_notifications.merge!(mapping_from_string(result['muting_notifications']))
      @requested.merge!(mapping_from_string(result['requested']))
      @domain_blocking.merge!(mapping_from_string(result['domain_blocking']))
      @endorsed.merge!(mapping_from_string(result['endorsed']))
      @account_note.merge!(mapping_from_json(result['account_note']))

      cache_uncached!
    end

    @following.merge!(options[:following_map] || {})
    @showing_reblogs.merge!(options[:showing_reblogs_map] || {})
    @notifying.merge!(options[:notifying_map] || {})
    @delivery_following.merge!(options[:delivery_following_map] || {})
    @followed_by.merge!(options[:followed_by_map] || {})
    @subscribing.merge!(options[:subscribing_map] || {})
    @blocking.merge!(options[:blocking_map] || {})
    @blocked_by.merge!(options[:blocked_by_map] || {})
    @muting.merge!(options[:muting_map] || {})
    @requested.merge!(options[:requested_map] || {})
    @domain_blocking.merge!(options[:domain_blocking_map] || {})
    @endorsed.merge!(options[:endorsed_map] || {})
    @account_note.merge!(options[:account_note_map] || {})
  end

  private

  def mapping_from_string(string)
    return {} if string.blank?

    string&.split(',')&.map(&:to_i)&.index_with(true) || {}
  end

  def mapping_from_json(json)
    return {} if json.blank?

    (Oj.load(json, mode: :strict, symbol_keys: true) || {}).tap do |json_data|
      json_data.keys.each do |key|
        json_data[(Integer(key.to_s) rescue key) || key] = json_data.delete(key)
      end
    end
  end

  def cached
    return @cached if defined?(@cached)

    @cached = {
      following: {},
      showing_reblogs: {},
      notifying: {},
      delivery_following: {},
      followed_by: {},
      subscribing: {},
      blocking: {},
      blocked_by: {},
      muting: {},
      muting_notifications: {},
      requested: {},
      domain_blocking: {},
      endorsed: {},
      account_note: {},
    }

    @uncached_account_ids = []

    @account_ids.each do |account_id|
      maps_for_account = Rails.cache.read("relationship:#{@current_account_id}:#{account_id}")

      if maps_for_account.is_a?(Hash)
        @cached.deep_merge!(maps_for_account)
      else
        @uncached_account_ids << account_id
      end
    end

    @cached
  end

  def cache_uncached!
    @uncached_account_ids.each do |account_id|
      maps_for_account = {
        following:            { account_id => following[account_id] },
        showing_reblogs:      { account_id => showing_reblogs[account_id] },
        notifying:            { account_id => notifying[account_id] },
        delivery_following:   { account_id => delivery_following[account_id] },
        followed_by:          { account_id => followed_by[account_id] },
        subscribing:          { account_id => subscribing[account_id] },
        blocking:             { account_id => blocking[account_id] },
        blocked_by:           { account_id => blocked_by[account_id] },
        muting:               { account_id => muting[account_id] },
        muting_notifications: { account_id => muting_notifications[account_id] },
        requested:            { account_id => requested[account_id] },
        domain_blocking:      { account_id => domain_blocking[account_id] },
        endorsed:             { account_id => endorsed[account_id] },
        account_note:         { account_id => account_note[account_id] },
      }

      Rails.cache.write("relationship:#{@current_account_id}:#{account_id}", maps_for_account, expires_in: 1.day)
    end
  end
end
