require 'open_food_network/permissions'
require 'open_food_network/proxy_order_syncer'
require 'open_food_network/order_cycle_form_applicator'

class OrderCycleForm
  def initialize(order_cycle, params, user)
    @order_cycle = order_cycle
    @params = params
    @user = user
    @permissions = OpenFoodNetwork::Permissions.new(user)
  end

  def save
    build_schedule_ids
    order_cycle.assign_attributes(params[:order_cycle])
    return false unless order_cycle.valid?

    order_cycle.transaction do
      order_cycle.save!
      apply_exchange_changes
      sync_subscriptions
      true
    end
  rescue ActiveRecord::RecordInvalid
    false
  end

  private

  attr_accessor :order_cycle, :params, :user, :permissions

  def apply_exchange_changes
    return if exchanges_unchanged?

    OpenFoodNetwork::OrderCycleFormApplicator.new(order_cycle, user).go!
  end

  def exchanges_unchanged?
    [:incoming_exchanges, :outgoing_exchanges].all? do |direction|
      params[:order_cycle][direction].nil?
    end
  end

  def schedule_ids?
    params[:order_cycle][:schedule_ids].present?
  end

  def build_schedule_ids
    return unless schedule_ids?

    result = existing_schedule_ids
    result |= (requested_schedule_ids & permitted_schedule_ids) # Add permitted and requested
    result -= ((result & permitted_schedule_ids) - requested_schedule_ids) # Remove permitted but not requested
    params[:order_cycle][:schedule_ids] = result
  end

  def sync_subscriptions
    return unless schedule_ids?
    return unless schedule_sync_required?

    OpenFoodNetwork::ProxyOrderSyncer.new(subscriptions_to_sync).sync!
  end

  def schedule_sync_required?
    removed_schedule_ids.any? || new_schedule_ids.any?
  end

  def subscriptions_to_sync
    Subscription.where(schedule_id: removed_schedule_ids + new_schedule_ids)
  end

  def requested_schedule_ids
    params[:order_cycle][:schedule_ids].map(&:to_i)
  end

  def permitted_schedule_ids
    Schedule.where(id: requested_schedule_ids | existing_schedule_ids)
      .merge(permissions.editable_schedules).pluck(:id)
  end

  def existing_schedule_ids
    @existing_schedule_ids ||= order_cycle.persisted? ? order_cycle.schedule_ids : []
  end

  def removed_schedule_ids
    existing_schedule_ids - order_cycle.schedule_ids
  end

  def new_schedule_ids
    @order_cycle.schedule_ids - existing_schedule_ids
  end
end
