# frozen_string_literal: true

# rubocop:disable Metrics/ModuleLength
module Reporting
  RSpec.describe ReportTemplate do
    let(:user) { create(:user) }
    let(:params) { {} }
    subject { described_class.new(user, params) }

    # rubocop:disable Metrics/AbcSize
    def check_report
      # Mock using instance variables
      allow(subject).to receive(:columns).and_return(@columns)
      allow(subject).to receive(:query_result).and_return(@query_result)
      allow(subject).to receive(:rules).and_return(@rules) if @rules.present?
      if @custom_headers.present?
        allow(subject).to receive(:custom_headers).and_return(@custom_headers)
      end

      # Check result depending on existing instance variables
      expect(subject.rows.map(&:to_h)).to eq(@expected_rows) if @expected_rows.present?
      expect(subject.table_rows).to eq(@expected_table_rows) if @expected_table_rows.present?
      expect(subject.table_headers).to eq(@expected_headers) if @expected_headers.present?
    end
    # rubocop:enable Metrics/AbcSize

    describe ".default_params" do
      it "use correctly the default values" do
        default_params = {
          filter: "default__filter",
          other_filter: "default_other_filter",
          q: { hub: "default_hub", customer: "default_customer" }
        }
        real_params = {
          filter: "test_filter",
          q: { hub: "test_hub" }
        }
        expected_params = {
          filter: "test_filter",
          other_filter: "default_other_filter",
          q: { hub: "test_hub", customer: "default_customer" }
        }
        allow_any_instance_of(described_class).to receive(:default_params)
          .and_return(default_params)
        report = described_class.new(user, real_params)
        expect(report.params).to eq(expected_params)
      end
    end

    describe ".columns" do
      before do
        @query_result = [
          OpenStruct.new(hub: { name: "My Hub" }, product: { name: "Apple", price: 5 })
        ]
      end

      it "handle procs" do
        @columns = {
          hub: proc { |item| item.hub[:name] }
        }
        @expected_rows = [
          { hub: "My Hub" }
        ]
        check_report
      end

      it "handles symbols" do
        @columns = {
          hub: :hub_name
        }
        allow(subject).to receive(:hub_name).and_return("Transformed Hub Name")
        @expected_rows = [
          { hub: "Transformed Hub Name" }
        ]
        check_report
      end
    end

    describe ".table_headers" do
      before do
        @columns = {
          hub: proc { |item| item.hub[:name] },
          product: proc { |item| item.product[:name] },
          price: proc { |item| item.product[:price] },
        }
      end

      it "uses the columns keys" do
        @expected_headers = ['Hub', 'Product', 'Price']
        check_report
      end

      it "handles custom_headers" do
        @custom_headers = {
          product: 'Custom Product',
          not_existing_key: "My Key"
        }
        @expected_headers = ['Hub', 'Custom Product', 'Price']
        check_report
      end

      describe "fields_to_show" do
        let(:params) { { fields_to_show: [:hub, :price] } }

        it "works" do
          @expected_headers = ['Hub', 'Price']
          check_report
        end
      end
    end

    describe ".table_rows" do
      before do
        @columns = {
          price: proc { |item| item.product[:price] },
          hub: proc { |item| item.hub[:name] }
        }
        @query_result = [
          OpenStruct.new(hub: { name: "My Hub" }, product: { name: "Apple", price: 5 }),
          OpenStruct.new(hub: { name: "My Other Hub" }, product: { name: "Apple", price: 12 })
        ]
      end

      it "get correct data" do
        allow(subject).to receive(:unformatted_render?).and_return(true)
        @expected_table_rows = [
          [5, "My Hub"],
          [12, "My Other Hub"],
        ]
        check_report
      end

      context "when report contains duplicate rows" do
        before do
          @columns = {
            customer: proc { |item| item.customer },
            address: proc { |item| item.address }
          }
          @query_result = [
            OpenStruct.new(customer: "John", address: "1 Main Street"),
            OpenStruct.new(customer: "John", address: "1 Main Street")
          ]
        end

        context "and the report type allows duplicate rows i.e. the default behaviour" do
          it "outputs duplicate rows" do
            @expected_table_rows = [
              ["John", "1 Main Street"],
              ["John", "1 Main Street"]
            ]
            check_report
          end
        end

        context "and the report type does not allow duplicate rows" do
          before { allow(subject).to receive(:skip_duplicate_rows?).and_return(true) }

          it "outputs only unique rows" do
            @expected_table_rows = [
              ["John", "1 Main Street"]
            ]
            check_report
          end
        end
      end
    end

    describe ".rules" do
      describe "#group_by" do
        before do
          @columns = {
            hub: proc { |item| item.hub },
            customer: proc { |item| item.customer },
            product: proc { |item| item.product },
            quantity: proc { |item| item.quantity },
          }
          @query_result = [
            OpenStruct.new(hub: "Hub 1", customer: "John", product: "Apple", quantity: 4),
            OpenStruct.new(hub: "Hub 2", customer: "John", product: "Pear", quantity: 3),
            OpenStruct.new(hub: "Hub 2", customer: "John", product: "Apple", quantity: 5),
            OpenStruct.new(hub: "Hub 1", customer: "Abby", product: "Orange", quantity: 6),
          ]
        end

        it "works with symbol or proc" do
          @rules = [
            { group_by: proc { |_i, row| row.hub }, fields_used_in_header: [:hub], header: true },
            { group_by: :customer, header: true }
          ]
          allow(subject).to receive(:display_header_row?).and_return(true)
          @expected_rows = [
            { header: "Hub 1" },
            { header: "Abby" },
            { product: "Orange", quantity: 6 },
            { header: "John" },
            { product: "Apple", quantity: 4 },
            { header: "Hub 2" },
            { header: "John" },
            { product: "Pear", quantity: 3 },
            { product: "Apple", quantity: 5 },
          ]
          check_report
        end
      end

      describe "#sort_by" do
        before do
          @columns = {
            hub_name: proc { |item| item.hub[:name] }
          }
          hub1 = { name: "Hub 1", popularity: 5 }
          hub2 = { name: "Hub 2", popularity: 2 }
          @query_result = [
            OpenStruct.new(hub: hub2),
            OpenStruct.new(hub: hub1)
          ]
        end

        it "use default sort" do
          @rules = [{
            group_by: proc { |item, _row| item.hub }
          }]
          @expected_rows = [
            { hub_name: "Hub 1" },
            { hub_name: "Hub 2" },
          ]
          check_report
        end

        it "use sort_by proc" do
          @rules = [{
            group_by: proc { |item, _row| item.hub },
            sort_by: proc { |hub| hub[:popularity] }
          }]
          @expected_rows = [
            { hub_name: "Hub 2" },
            { hub_name: "Hub 1" }
          ]
          check_report
        end
      end

      describe "#summary_row" do
        before do
          @query_result = [
            OpenStruct.new(hub: "Hub 1", customer: "John", product: "Apple", quantity: 4),
            OpenStruct.new(hub: "Hub 2", customer: "John", product: "Pear", quantity: 3),
            OpenStruct.new(hub: "Hub 2", customer: "John", product: "Apple", quantity: 5),
            OpenStruct.new(hub: "Hub 1", customer: "Abby", product: "Orange", quantity: 6),
          ]
        end

        it "groups and sum" do
          @columns = {
            hub: proc { |item| item.hub },
            quantity: proc { |item| item.quantity },
            count: proc { |_item| "" },
          }
          @rules = [{
            group_by: :hub,
            summary_row: proc do |group_key, items, rows|
              { count: "#{group_key} count=#{items.count}", quantity: rows.sum(&:quantity) }
            end,
            summary_row_label: "TOTAL"
          }]
          @expetec_rows = [
            { hub: "Hub 1", quantity: 4, count: "" },
            { hub: "Hub 1", quantity: 6, count: "" },
            { hub: "TOTAL", quantity: 10, count: "Hub 1 count=2" },
            { hub: "Hub 2", quantity: 3, count: "" },
            { hub: "Hub 2", quantity: 5, count: "" },
            { hub: "TOTAL", quantity: 8, count: "Hub 2 count=2" }
          ]
          check_report
        end
      end

      describe "should not group when for JSON" do
        before do
          @query_result = [
            OpenStruct.new(hub: "Hub 1", customer: "John", quantity: 4)
          ]
          @columns = {
            hub: proc { |item| item.hub },
            customer: proc { |item| item.customer },
            quantity: proc { |item| item.quantity },
          }
          @rules = [{
            group_by: :hub,
            header: true,
            summary_row: proc do |_group_key, _items, rows|
              { quantity: rows.sum(&:quantity) }
            end
          }]
        end

        describe "for fields_to_show" do
          let(:params) { { fields_to_show: [:hub, :quantity], report_format: 'json' } }

          it "works" do
            @expetec_rows = [
              { hub: "Hub 1", quantity: 4 }
            ]
            check_report
          end
        end

        describe "for fields_to_hide" do
          let(:params) { { fields_to_hide: [:customer], report_format: 'json' } }

          it "works" do
            @expetec_rows = [
              { hub: "Hub 1", quantity: 4 }
            ]
            check_report
          end
        end
      end
    end
  end
end
# rubocop:enable Metrics/ModuleLength
