require 'test_helper'

module Workarea
  module DataFile
    class CsvTest < TestCase
      class Foo
        include ApplicationDocument
        field :name, type: String
        field :local, type: Boolean, localize: true
        field :hash_test, type: Hash
        field :payment, type: ActiveMerchant::Billing::Response
        field :validated_field, type: String

        embeds_many :bars
        embeds_many :bazzets
        embeds_one :baz

        cattr_accessor :validation_failure
        validate :validated_field_is_valid

        def validated_field_is_valid
          errors.add(:base, 'error!') if validation_failure
        end
      end

      class Bar
        include ApplicationDocument
        field :name, type: String
        field :array, type: Array
        field :ignore, type: Integer
        field :hash_test, type: Hash
        embedded_in :foo
      end

      class Baz
        include ApplicationDocument
        field :name, type: String
        field :hash_test, type: Hash
        embedded_in :foo
      end

      class Bazzet
        include ApplicationDocument
        extend Dragonfly::Model
        field :image_uid, type: String
        dragonfly_accessor :image, app: :workarea
        embedded_in :foo
      end

      def test_merging_rows_for_embedded
        model = Foo.create!(
          bars: [{ name: '1' }, { name: '2' }],
          baz: { name: '3' }
        )

        csv = Csv.new.serialize(model)
        results = CSV.parse(csv, headers: :first_row).map(&:to_h)

        assert_equal(2, results.size)
        results.each { |r| assert_equal(model.id.to_s, r['_id']) }
        assert_equal('1', results.first['bars_name'])
        assert_equal('2', results.second['bars_name'])
        assert_equal('3', results.first['baz_name'])
      end

      def test_handles_blank_embedded
        model = Foo.create!(name: '1')
        csv = Csv.new.serialize(model)
        results = CSV.parse(csv, headers: :first_row).map(&:to_h)

        assert_equal(1, results.size)
        assert_equal(model.id.to_s, results.first['_id'])
        assert_equal('1', results.first['name'])
      end

      def test_handles_localized_fields
        set_locales(available: [:en, :es], default: :en, current: :es)

        model = Foo.create!(local_translations: { en: true, es: false })
        model.local = true

        import = create_import(
          model_type: Foo.name,
          file: create_tempfile(Csv.new.serialize(model), extension: 'csv'),
          file_type: 'csv'
        )

        results = CSV.read(import.file.path, headers: :first_row).map(&:to_h)

        assert_equal(1, results.size)
        assert_equal(model.id.to_s, results.first['_id'])
        assert_equal('true', results.first['local'])

        csv = Csv.new(import).import!
        assert(model.reload.local)
      end

      def test_arrays
        model = Foo.create!(bars: [{ array: %w(1 2) }])
        model.bars.first.array = %w(3 4)

        import = create_import(
          model_type: Foo.name,
          file: create_tempfile(Csv.new.serialize(model), extension: 'csv'),
          file_type: 'csv'
        )

        results = CSV.read(import.file.path, headers: :first_row).map(&:to_h)

        assert_equal(1, results.size)
        assert_equal(model.id.to_s, results.first['_id'])
        assert_equal('3,4', results.first['bars_array'])

        Csv.new(import).import!
        assert_equal(%w(3 4), model.reload.bars.first.array)
      end

      def test_hashes
        model = Foo.create!(baz: { hash_test: { 'foo' => %w(1 2) } })
        model.baz.hash_test = { 'foo' => %w(3 4), 'bar' => '5' }

        import = create_import(
          model_type: Foo.name,
          file: create_tempfile(Csv.new.serialize(model), extension: 'csv'),
          file_type: 'csv'
        )

        results = CSV.read(import.file.path, headers: :first_row).map(&:to_h)

        assert_equal(1, results.size)
        assert_equal(model.id.to_s, results.first['_id'])
        assert_equal('3,4', results.first['baz_hash_test_foo'])
        assert_equal('5', results.first['baz_hash_test_bar'])

        Csv.new(import).import!
        assert_equal(%w(3 4), model.reload.baz.hash_test['foo'])
        assert_equal('5', model.reload.baz.hash_test['bar'])
      end

      def test_handles_variable_numbers_of_hashes
        one = Foo.create!(baz: { hash_test: { 'foo' => %w(1 2) } })
        two = Foo.create!(baz: { hash_test: { 'bar' => %w(3 4) } })

        import = create_import(
          model_type: Foo.name,
          file: create_tempfile(Csv.new.serialize([one, two]), extension: 'csv'),
          file_type: 'csv'
        )

        results = CSV.read(import.file.path, headers: :first_row).map(&:to_h)

        assert_equal(2, results.size)
        assert_equal(one.id.to_s, results.first['_id'])
        assert_equal('1,2', results.first['baz_hash_test_foo'])
        assert(results.first['baz_hash_test_bar'].blank?)

        assert_equal(two.id.to_s, results.second['_id'])
        assert_equal('3,4', results.second['baz_hash_test_bar'])
        assert(results.second['baz_hash_test_foo'].blank?)

        Csv.new(import).import!
        assert_equal(%w(1 2), one.reload.baz.hash_test['foo'])
        assert_equal(%w(3 4), two.reload.baz.hash_test['bar'])
      end

      def test_complex_embedded_hashes
        model = Foo.create!(
          baz: { hash_test: { 'foo' => %w(1 2) } },
          bars: [
            { hash_test: { 'bar' => %w(1 2) } },
            { hash_test: { 'bar' => %w(3 4) } }
          ]
        )
        model.baz.hash_test = { 'foo' => %w(3 4), 'bar' => '5' }
        model.bars.first.hash_test = { 'foo' => %w(6 7) }
        model.bars.second.hash_test = { 'bar' => %w(8 9), 'baz' => %(10), 'Qoo' => 'qux' }

        import = create_import(
          model_type: Foo.name,
          file: create_tempfile(Csv.new.serialize(model), extension: 'csv'),
          file_type: 'csv'
        )

        results = CSV.read(import.file.path, headers: :first_row).map(&:to_h)

        assert_equal(2, results.size)
        assert_equal(model.id.to_s, results.first['_id'])
        assert_equal('3,4', results.first['baz_hash_test_foo'])
        assert_equal('5', results.first['baz_hash_test_bar'])
        assert_equal('6,7', results.first['bars_hash_test_foo'])
        assert_nil(results.first['bars_hash_test_bar'])
        assert_nil(results.second['bars_hash_test_foo'])
        assert_equal('8,9', results.second['bars_hash_test_bar'])
        assert_equal('10', results.second['bars_hash_test_baz'])
        assert_equal('qux', results.second['bars_hash_test_Qoo'])

        Csv.new(import).import!
        model.reload

        assert_nil(model.hash_test)
        assert_equal(%w(3 4), model.baz.hash_test['foo'])
        assert_equal('5', model.baz.hash_test['bar'])
        assert_equal(2, model.bars.first.hash_test.size)
        assert_equal(%w(6 7), model.bars.first.hash_test['foo'])
        assert_equal(%w(1 2), model.bars.first.hash_test['bar'])
        assert_equal(3, model.bars.second.hash_test.size)
        assert_equal(%w(8 9), model.bars.second.hash_test['bar'])
        assert_equal('10', model.bars.second.hash_test['baz'])
        assert_equal('qux', model.bars.second.hash_test['Qoo'])
      end

      def test_ignored_fields
        Workarea.config.data_file_ignored_fields = %w(ignore)

        model = Foo.create!(
          bars: [{ name: '1', ignore: 1 }, { name: '2', ignore: 2 }]
        )

        csv = Csv.new.serialize(model)
        results = CSV.parse(csv, headers: :first_row).map(&:to_h)

        assert_equal(2, results.size)
        results.each { |r| refute(r.key?('bars_ignore')) }
      end

      def test_active_merchant_responses
        model = Foo.create!(payment: ActiveMerchant::Billing::Response.new(true, 'foo'))

        csv = Csv.new.serialize(model)
        results = CSV.parse(csv, headers: :first_row).map(&:to_h)

        assert_equal(1, results.size)
        assert_equal('true', results.first['payment_success'])
        assert_equal('foo', results.first['payment_message'])
        assert_nil(results.first['payment_options_cvv_result'])
      end

      def test_failed_new_record_rows_with_subsequent_rows
        Foo.validation_failure = true
        model = Foo.new(bars: [{ name: 'one' }, { name: 'two' }])

        import = create_import(
          model_type: Foo.name,
          file: create_tempfile(Csv.new.serialize(model), extension: 'csv'),
          file_type: 'csv'
        )

        Csv.new(import).import!
        assert_equal(0, Foo.count)
        assert_equal(2, import.total)
        assert_equal(0, import.succeeded)
        assert(import.file_errors['2'].present?)
        refute(import.file_errors.key?('3'))

      ensure
        Foo.validation_failure = false
      end

      def test_blank_ids_on_embedded_documents
        model = Foo.new(
          bars: [{ id: '', name: '1' }, { id: '', name: '2' }],
          baz: { id: '', name: '3' }
        )

        csv = Csv.new.serialize(model)
        results = CSV.parse(csv, headers: :first_row).map(&:to_h)
        assert(results.all? { |r| r['bars_id'].blank? && r['baz_id'].blank? })

        import = create_import(
          model_type: Foo.name,
          file: create_tempfile(csv, extension: 'csv'),
          file_type: 'csv'
        )

        Csv.new(import).import!
        assert_equal(1, Foo.count)

        model = Foo.first
        assert(model.bars.map(&:id).all?(&:present?))
        assert(model.baz.id.present?)
        assert_equal(2, model.bars.size)
      end

      def test_embedded_documents_get_timestamps
        model = Foo.new(
          bars: [{ name: '1' }, { name: '2' }],
          baz: { name: '3' }
        )

        import = create_import(
          model_type: Foo.name,
          file: create_tempfile(Csv.new.serialize(model), extension: 'csv'),
          file_type: 'csv'
        )

        Csv.new(import).import!
        assert_equal(1, Foo.count)

        model.reload
        assert(model.bars.present?)
        assert(model.bars.map(&:created_at).all?(&:present?))
        assert(model.bars.map(&:updated_at).all?(&:present?))
        assert(model.baz.created_at.present?)
        assert(model.baz.updated_at.present?)
      end

      def test_blank_rows_are_ignored
        model = Foo.new(id: nil)

        import = create_import(
          model_type: Foo.name,
          file: create_tempfile(Csv.new.serialize(model), extension: 'csv'),
          file_type: 'csv'
        )

        Csv.new(import).import!
        assert_equal(0, Foo.count)
      end

      def test_assign_dragonfly_attachment_attributes
        VCR.use_cassette :csv_image_upload do
          csv = "_id,bazzets_image_url\n,http://via.placeholder.com/350x150"
          import = create_import(
            model_type: Foo.name,
            file: create_tempfile(csv, extension: 'csv'),
            file_type: 'csv'
          )

          Csv.new(import).import!

          assert_equal(1, Foo.count)
          foo = Foo.first
          assert_equal(1, foo.bazzets.count)
          refute_nil(foo.bazzets.first.image_uid)
        end
      end

      def test_assign_password_when_provided
        csv = "_id,email,password\n,test@example.com,password1"
        import = create_import(
          model_type: User.name,
          file: create_tempfile(csv, extension: 'csv'),
          file_type: 'csv'
        )

        assert_difference -> { User.count } do
          Csv.new(import).import!
          user = User.find_by_email('test@example.com')

          assert user.present?, 'user not imported'
          assert user.authenticate('password1'), 'password not imported'
        end
      end

      def test_randomize_password_when_missing_and_new_record
        csv = "_id,email,password\n,missing@example.com"
        import = create_import(
          model_type: User.name,
          file: create_tempfile(csv, extension: 'csv'),
          file_type: 'csv'
        )

        assert_difference -> { User.count } do
          Csv.new(import).import!
          user = User.find_by_email('missing@example.com')

          assert user.present?, 'user not imported'
          refute user.authenticate('password1'),
            "password authenticated when it shouldn't have"
        end
      end

      def test_ignore_password_column_when_user_exists
        user = create_user(first_name: 'Foo')
        csv = "_id,email,password,first_name\n#{user.id},#{user.email},,Bar"
        import = create_import(
          model_type: User.name,
          file: create_tempfile(csv, extension: 'csv'),
          file_type: 'csv'
        )

        Csv.new(import).import!

        assert_equal 'Bar', user.reload.first_name
      end

      def test_encoding_conversion
        Workarea.with_config do |config|
          config.csv_import_options[:encoding] = 'ISO-8859-1'
          csv = %(_id,name,slug,details_keywords\n653911,Bowl Light#{153.chr},Bowl_Light,"testing")

          import = create_import(
            model_type: Catalog::Product.name,
            file: create_tempfile(csv, extension: 'csv', encoding: 'ascii-8bit'),
            file_type: 'csv'
          )

          Csv.new(import).import!

          product = Catalog::Product.find_by(slug: 'bowl_light')
          assert_equal("Bowl Light\u0099", product.name)
        end
      end
    end
  end
end