class Admin::Accounting::TransactionsController < Admin::Accounting::Controller
  include DateRangeHelper

  before_action :verify_charge_or_credit!, only: :update

  def index
    data = set_base(family || student || current_school)
      .preload(
        :family,
        :students,
        :tags,
        :billing_processed_transaction,
        subcategories: :category
      )
      .without_void(params[:void]&.to_bool)
      .with_action(@action)
      .with_categories(params[:category_id])
      .with_date_range(date_range(params[:dates]))
      .with_tags(params[:tag_ids])
      .with_student(params[:student_id])
      .with_families(params[:family_id])
      .with_or_without_invoice(params[:with_invoice]&.to_bool)
      .distinct

    render_success :ok, json: data.map { |d| transaction_props(d) }
  end

  def show
    render_success :ok, json: transaction_props(transaction)
  end

  def update
    transaction.assign_attributes(transaction_params)
    transaction_detail.assign_attributes(amount: params[:amount], subcategory: subcategory)

    if transaction.remove_allocations_and_update(transaction_detail)
      allocate_funds
      render_success :ok
    else
      render_error :unprocessable_entity, errors: transaction
    end
  end

  def destroy
    if transaction.destroy
      render_success :ok
    else
      render_error :unprocessable_entity, errors: transaction
    end
  end

  def batch_destroy
    to_delete = transactions
      .where(id: params[:ids])
      .with_or_without_invoice(false)
      .where.not(action: 2)
      .load

    count = to_delete.size

    if transactional_destroy(to_delete)
      render_success :ok, message: "#{count} transactions deleted."
    else
      render_error :unprocessable_entity
    end
  end

  def void
    if transaction.update(void: params[:void])
      label = if transaction.void
        'voided'
      else
        allocate_funds
        'unvoided'
      end

      render_success :ok, message: "Transaction #{label}."
    else
      render_error :unprocessable_entity, errors: transaction
    end
  end

  def distribution
    if family
      @decrease = family.accounting_decreases.build(distribution_params)
      render_success :ok, json: distribution_props
    else
      render_error :unprocessable_entity, error: 'Family must exist'
    end
  end

  def aging
    Reporting::Accounting::TransactionAgingJob.perform_async(
      current_school.id,
      current_user.id,
      type: params[:type]
    )
    render_success :ok
  end

  def report
    Reporting::Accounting::TransactionReportJob.perform_async(
      current_school.id,
      current_user.id,
      dates: params[:dates],
      types: params[:types],
      category_ids: params[:category_ids],
      tag_ids: params[:tag_ids],
      statement_type: params[:statement_type]
    )
    render_success :ok
  end

  def balance_summary
    Reporting::Accounting::SchoolBalanceJob.perform_async(
      current_school.id,
      current_user.id,
      type: params[:statement_type],
      category_ids: params[:category_ids],
      group_by: params[:group_by]
    )
    render_success :ok
  end

  private
    def family
      @family ||= current_school.families.find_by(id: params[:family_id])
    end

    def student
      @student ||= current_school.students.find_by(id: params[:student_id])
    end

    def transactions
      current_school.accounting_transactions
    end

    def transaction
      @transaction ||= transactions.find_by(id: params[:id])
    end

    def transaction_detail
      @transaction_detail ||= transaction.transaction_details.first
    end

    def type
      @type ||= params[:type].present? ? params[:type].to_sym : nil
    end

    def subcategory
      @subcategory ||= current_school.accounting_subcategories.find_by(id: params[:subcategory_id])
    end

    def distribution_service
      @distribution_service ||= Accounting::TransactionsService.new(family, @decrease)
        .distribution(subcategory)
    end

    def set_base(model)
      if type == :transfer
        @action = 2
        model.accounting_transactions
      elsif [:charge, :refund].include?(type)
        @action = Accounting::Increase.actions[type]
        model.accounting_transactions.only_increases
      elsif [:payment, :credit].include?(type)
        @action = Accounting::Decrease.actions[type]
        model.accounting_transactions.only_decreases
      else
        model.accounting_transactions
      end
    end

    def distribution_props
      distribution_service[:subcategories].map do |subcategory, balance|
        {}.tap do |prop|
          prop[:subcategory_id] = subcategory.id
          prop[:name] = subcategory.decorate.title
          prop[:balance] = balance.to_f
          prop[:applied] = distribution_service[:auto][subcategory].to_f if @decrease.payment?
          prop[:students] = student_props(subcategory)
        end
      end
    end

    def student_props(subcategory)
      distribution_service[:students].map do |student, data|
        {}.tap do |prop|
          prop[:student_id] = student.id
          prop[:first_name] = student.first_name
          if @decrease.payment?
            prop[:balance] = data[:balance][subcategory].to_f
            prop[:applied] = data[:auto][subcategory].to_f
          else
            prop[:balance] = data[subcategory].to_f
          end
        end
      end
    end

    def distribution_params
      params.permit(:amount).merge(action: subcategory ? :transfer : :payment)
    end

    def verify_charge_or_credit!
      return if [:charge, :credit].include?(transaction.action.to_sym)

      render_error :unprocessable_entity, message: 'Transaction cannot be updated'
    end

    def allocate_funds
      AllocationJob.perform_now(
        transaction.family,
        subcategory || transaction_detail.subcategory,
        student: transaction_detail.student,
        audit_user: current_user
      )
    end

    def transaction_params
      params.permit(:posted_on, :amount, :memo, :void).merge(tag_ids: params[:tag_ids])
    end

    def transaction_props(transaction)
      {
        id: transaction.id,
        date: transaction.decorate.posted_on,
        created_at: transaction.created_at,
        memo: transaction.memo,
        family: transaction.decorate.family_name,
        student: transaction.student_name,
        students: transaction.decorate.student_names,
        subcategory_id: transaction.subcategories.first.id,
        category: transaction.category_name,
        categories: transaction.decorate.subcategory_names,
        amount: transaction.decorate.amount,
        action: transaction.action.humanize,
        type: transaction.class.name.demodulize,
        void: transaction.void,
        tag_ids: transaction.tag_ids,
        billing_transaction: !!transaction.billing_processed_transaction
      }
    end
end
