class LettersController < ApplicationController
  before_action :set_letter, except: %i[ index new create ]

  # GET /letters
  def index
    authorize Letter
    # Get all letters with their associations using policy scope
    @all_letters = policy_scope(Letter).includes(:batch, :address, :usps_mailer_id, :label_attachment, :label_blob)
      .where.not(aasm_state: "queued")
      .order(created_at: :desc)

    # Get unbatched letters with pagination
    @unbatched_letters = @all_letters.not_in_batch.page(params[:page]).per(20)

    # Get batched letters grouped by batch
    @batched_letters = @all_letters.in_batch.group_by(&:batch)
  end

  # GET /letters/1
  def show
    authorize @letter
    @available_templates = SnailMail::PhlexService.available_templates
  end

  # GET /letters/new
  def new
    authorize Letter
    @letter = Letter.new
    @letter.return_address = current_user.home_return_address || ReturnAddress.first
    @letter.build_address
  end

  # GET /letters/1/edit
  def edit
    authorize @letter
    # If letter doesn't have a return address already, don't build one
    # Let the user select one from the dropdown
  end

  # POST /letters
  def create
    @letter = Letter.new(letter_params.merge(user: current_user))
    authorize @letter

    # Set postage type to international_origin if return address is not US
    if @letter.return_address && @letter.return_address.country != "US"
      @letter.postage_type = "international_origin"
    end

    if @letter.save
      redirect_to @letter, notice: "Letter was successfully created."
    else
      render :new, status: :unprocessable_entity
    end
  end

  # PATCH/PUT /letters/1
  def update
    authorize @letter

    if @letter.batch_id.present? && params[:letter][:postage_type].present?
      redirect_to @letter, alert: "Cannot change postage type for a letter that is part of a batch."
      return
    end

    # Set postage type to international_origin if return address is not US
    if params[:letter][:return_address_id].present?
      return_address = ReturnAddress.find(params[:letter][:return_address_id])
      if return_address.country != "US"
        params[:letter][:postage_type] = "international_origin"
      end
    end

    if @letter.update(letter_params)
      redirect_to @letter, notice: "Letter was successfully updated."
    else
      render :edit, status: :unprocessable_entity
    end
  end

  # DELETE /letters/1
  def destroy
    authorize @letter
    @letter.destroy!
    redirect_to letters_path, status: :see_other, notice: "Letter was successfully destroyed."
  end

  # POST /letters/1/generate_label
  def generate_label
    authorize @letter, :generate_label?
    template = params[:template]
    include_qr_code = params[:qr].present?

    # Generate label with specified template
    begin
      # Let the model method handle saving itself
      if @letter.generate_label(template:, include_qr_code:)
        if @letter.label.attached?
          # Redirect back to the letter page with a success message
          redirect_to @letter, notice: "Label was successfully generated."
        else
          redirect_to @letter, alert: "Failed to generate label."
        end
      else
        redirect_to @letter, alert: "Failed to generate label: #{@letter.errors.full_messages.join(", ")}"
      end
    rescue => e
      raise
      redirect_to @letter, alert: "Error generating label: #{e.message}"
    end
  end

  def preview_template
    authorize @letter, :preview_template?
    template = params["template"]
    include_qr_code = params["qr"].present?
    send_data SnailMail::PhlexService.generate_label(@letter, { template:, include_qr_code: }).render, type: "application/pdf", disposition: "inline"
  end

  # POST /letters/1/mark_printed
  def mark_printed
    authorize @letter, :mark_printed?
    if @letter.mark_printed!
      redirect_to @letter, notice: "Letter has been marked as printed."
    else
      redirect_to @letter, alert: "Could not mark letter as printed: #{@letter.errors.full_messages.join(", ")}"
    end
  end

  # POST /letters/1/mark_mailed
  def mark_mailed
    authorize @letter, :mark_mailed?
    if @letter.mark_mailed!
      User::UpdateTasksJob.perform_now(current_user)
      redirect_to @letter, notice: "Letter has been marked as mailed."
    else
      redirect_to @letter, alert: "Could not mark letter as mailed: #{@letter.errors.full_messages.join(", ")}"
    end
  end

  # POST /letters/1/mark_received
  def mark_received
    authorize @letter, :mark_received?
    if @letter.mark_received!
      redirect_to @letter, notice: "Letter has been marked as received."
    else
      redirect_to @letter, alert: "Could not mark letter as received: #{@letter.errors.full_messages.join(", ")}"
    end
  end

  # POST /letters/1/clear_label
  def clear_label
    authorize @letter, :clear_label?
    if @letter.pending? && @letter.label.attached?
      @letter.label.purge
      redirect_to @letter, notice: "Label has been cleared."
    else
      redirect_to @letter, alert: "Cannot clear label: Letter is not in pending state or has no label attached."
    end
  end

  # POST /letters/1/buy_indicia
  def buy_indicia
    authorize @letter, :buy_indicia?
    if @letter.batch_id.present?
      redirect_to @letter, alert: "Cannot buy indicia for a letter that is part of a batch."
      return
    end

    if @letter.postage_type != "indicia"
      redirect_to @letter, alert: "Letter must be set to indicia postage type first."
      return
    end

    if @letter.usps_indicium.present?
      redirect_to @letter, alert: "Indicia already purchased for this letter."
      return
    end

    usps_payment_account = USPS::PaymentAccount.find_by(id: params[:usps_payment_account_id])
    if usps_payment_account.nil?
      redirect_to @letter, alert: "Please select a valid USPS payment account."
      return
    end

    hcb_payment_account = current_user.hcb_payment_accounts.find_by(id: params[:hcb_payment_account_id])

    if hcb_payment_account.blank?
      redirect_to @letter, alert: "You must select an HCB payment account to purchase indicia."
      return
    end

    indicium = USPS::Indicium.create!(
      letter: @letter,
      payment_account: usps_payment_account,
      hcb_payment_account: hcb_payment_account,
    )
    cost_cents = (@letter.postage * 100).ceil

    transfer_service = HCB::TransferService.new(
      hcb_payment_account: hcb_payment_account,
      amount_cents: cost_cents,
      name: "Postage for #{@letter.public_id} #{indicium.public_id} #{letter_path(@letter)}",
      memo: "[theseus] postage for a #{@letter.processing_category}",
    )
    transfer = transfer_service.call

    unless transfer
      indicium.destroy!
      redirect_to @letter, alert: transfer_service.errors.join(", ")
      return
    end

    indicium.update!(hcb_transfer_id: transfer.id)

    begin
      indicium.buy!
    rescue => e
      HCB::PaymentAccount.refund_to_organization!(
        organization_id: hcb_payment_account.organization_id,
        amount_cents: cost_cents,
        name: "Refund for #{@letter.public_id} #{indicium.public_id} #{letter_path(@letter)}",
        memo: "[theseus] postage refund for a #{@letter.processing_category}",
      )
      redirect_to @letter, alert: "Purchase failed: #{e.message}"
      return
    end

    redirect_to @letter, notice: "Indicia purchased successfully (charged to #{hcb_payment_account.organization_name})."
  end

  private

  # Use callbacks to share common setup or constraints between actions.
  def set_letter
    @letter = Letter.find_by_public_id!(params[:id])
  end

  # Only allow a list of trusted parameters through.
  def letter_params
    params.require(:letter).permit(
      :body,
      :height,
      :width,
      :weight,
      :non_machinable,
      :processing_category,
      :postage_type,
      :mailing_date,
      :rubber_stamps,
      :user_facing_title,
      :usps_mailer_id_id,
      :return_address_id,
      :return_address_name,
      :recipient_email,
      address_attributes: [
        :id,
        :first_name,
        :last_name,
        :line_1,
        :line_2,
        :city,
        :state,
        :postal_code,
        :country,
      ],
      return_address_attributes: [
        :id,
        :name,
        :line_1,
        :line_2,
        :city,
        :state,
        :postal_code,
        :country,
      ],
      tags: [],
    )
  end
end
