# frozen_string_literal: true

require 'spec_helper'

describe KPM::Account do
  shared_context 'account' do
    include_context 'connection_setup'

    let(:account_class) do
      described_class.new(nil, [killbill_api_key, killbill_api_secret],
                          [killbill_user, killbill_password], url,
                          db_name, [db_username, db_password], db_host, db_port, nil, logger)
    end
    let(:dummy_account_id) { SecureRandom.uuid }
    let(:account_id_invalid) { SecureRandom.uuid }
    let(:account_id) { creating_account_with_client }
    let(:dummy_data) do
      "-- accounts record_id|id|external_key|email|name|first_name_length|currency|billing_cycle_day_local|parent_account_id|is_payment_delegated_to_parent|payment_method_id|time_zone|locale|address1|address2|company_name|city|state_or_province|country|postal_code|phone|notes|migrated|is_notified_for_invoices|created_date|created_by|updated_date|updated_by|tenant_record_id\n"\
      "5|#{dummy_account_id}|#{dummy_account_id}|willharnet@example.com|Will Harnet||USD|0||||UTC||||Company\\N{VERTICAL LINE}\\N{LINE FEED}Name||||||||false|2017-04-03T15:50:14.000+0000|demo|2017-04-05T15:01:39.000+0000|Killbill::Stripe::PaymentPlugin|2\n"\
      "-- account_history record_id|id|target_record_id|external_key|email|name|first_name_length|currency|billing_cycle_day_local|parent_account_id|payment_method_id|is_payment_delegated_to_parent|time_zone|locale|address1|address2|company_name|city|state_or_province|country|postal_code|phone|notes|migrated|is_notified_for_invoices|change_type|created_by|created_date|updated_by|updated_date|tenant_record_id\n"\
      "3|#{SecureRandom.uuid}|5|#{dummy_account_id}|willharnet@example.com|Will Harnet||USD|0||||UTC||||Company\\N{VERTICAL LINE}\\N{LINE FEED}Name||||||||false|INSERT|demo|2017-04-03T15:50:14.000+0000|demo|2017-04-03T15:50:14.000+0000|2\n"
    end
    let(:cols_names) { dummy_data.split("\n")[0].split(' ')[2] }
    let(:cols_data) { dummy_data.split("\n")[1] }
    let(:table_name) { dummy_data.split("\n")[0].split(' ')[1] }
    let(:obfuscating_marker) { :email }
    let(:mysql_cli) { "mysql --user=#{db_username} --password=#{db_password} --host=#{db_host} --port=#{db_port} " }
    let(:test_ddl) { Dir["#{Dir.pwd}/**/account_test_ddl.sql"][0] }
  end

  describe '#initialize' do
    include_context 'account'

    context 'when creating an instance of account class' do
      it 'when initialized with defaults' do
        expect(described_class.new).to be_an_instance_of(KPM::Account)
      end

      it 'when initialized with options' do
        expect(account_class).to be_an_instance_of(KPM::Account)
        expect(account_class.instance_variable_get(:@killbill_api_key)).to eq(killbill_api_key)
        expect(account_class.instance_variable_get(:@killbill_api_secret)).to eq(killbill_api_secret)
        expect(account_class.instance_variable_get(:@killbill_user)).to eq(killbill_user)
        expect(account_class.instance_variable_get(:@killbill_password)).to eq(killbill_password)
        expect(account_class.instance_variable_get(:@killbill_url)).to eq(url)
      end
    end
  end

  # export data tests
  describe '#fetch_export_data' do
    include_context 'account'

    context 'when fetching account from api' do
      it 'when account id not found' do
        expect { account_class.send(:fetch_export_data, account_id_invalid) }.to raise_error(Interrupt, 'Account id not found')
      end

      it 'when account id found' do
        expect(account_id).to match(/\w{8}(-\w{4}){3}-\w{12}?/)
        expect { account_class.send(:fetch_export_data, account_id) }.not_to raise_error
        expect(account_class.send(:fetch_export_data, account_id)).to match(account_id)
      end
    end
  end

  describe '#process_export_data' do
    include_context 'account'

    context 'when processing data to export' do
      it 'when column name qty eq column data qty' do
        expect(account_class.send(:process_export_data, cols_data, table_name, cols_names.split('|')).split('|').size).to eq(cols_names.split('|').size)
      end

      it 'when obfuscating data' do
        marker_index = 0
        cols_names.split('|').each do |col_name|
          break if col_name.equal?(obfuscating_marker.to_s)

          marker_index += 1
        end

        obfuscating_marker_data = account_class.send(:process_export_data, cols_data, table_name, cols_names.split('|')).split('|')
        expect(obfuscating_marker_data[marker_index]).to be_nil
      end
    end
  end

  describe '#remove_export_data' do
    include_context 'account'

    it 'when obfuscating value' do
      expect(account_class.send(:remove_export_data, table_name, obfuscating_marker.to_s, 'willharnet@example.com')).to be_nil
    end
  end

  describe '#export' do
    include_context 'account'

    context 'when exporting data' do
      it 'when file created' do
        expect(File.exist?(account_class.send(:export, dummy_data))).to be_truthy
      end

      it 'when file contains account record' do
        expect(File.readlines(account_class.send(:export, dummy_data)).grep(/#{table_name}/)).to be_truthy
        expect(File.readlines(account_class.send(:export, dummy_data)).grep(/#{cols_names}/)).to be_truthy
      end
    end
  end

  describe '#export_data' do
    include_context 'account'

    context 'when exporting data; main method' do
      it 'when no account id' do
        expect { account_class.export_data }.to raise_error(Interrupt, 'Account id not found')
      end

      it 'when file created' do
        expect(account_id).to match(/\w{8}(-\w{4}){3}-\w{12}?/)
        expect(File.exist?(account_class.export_data(account_id))).to be_truthy
      end

      it 'when file contains account record' do
        expect(account_id).to match(/\w{8}(-\w{4}){3}-\w{12}?/)
        expect(File.readlines(account_class.export_data(account_id)).grep(/#{table_name}/)).to be_truthy
        expect(File.readlines(account_class.export_data(account_id)).grep(/#{cols_names}/)).to be_truthy
      end
    end
  end

  # import data tests
  describe '#sniff_delimiter' do
    include_context 'account'

    it 'when data delimiter is sniffed as "|"' do
      File.open(dummy_data_file, 'w') do |io|
        io.puts(dummy_data)
      end

      expect(account_class.send(:sniff_delimiter, dummy_data_file)).to eq('|')
    end
  end

  describe '#fill_empty_column' do
    include_context 'account'

    it 'when empty value' do
      expect(account_class.send(:fill_empty_column, '')).to eq(:DEFAULT)
    end
  end

  describe '#fix_dates' do
    include_context 'account'

    it 'when valid date value' do
      expect { DateTime.parse(account_class.send(:fix_dates, '2017-04-05T15:01:39.000+0000')) }.not_to raise_error
    end

    it 'when valid date value match YYYY-MM-DD HH:MM:SS' do
      expect(account_class.send(:fix_dates, '2017-04-05T15:01:39.000+0000')).to match(/^\d{4}-\d{1,2}-\d{1,2} \d{1,2}:\d{1,2}:\d{1,2}/)
    end

    it 'when invalid date value' do
      expect { DateTime.parse(account_class.send(:fix_dates, 'JO')) }.to raise_error(ArgumentError)
    end
  end

  describe '#replace_boolean' do
    include_context 'account'

    context 'when value is boolean; replace' do
      it 'when true' do
        expect(account_class.send(:replace_boolean, true)).to eq(1)
      end

      it 'when false' do
        expect(account_class.send(:replace_boolean, false)).to eq(0)
      end
    end
  end

  describe '#replace_account_record_id' do
    include_context 'account'

    it 'when field is account_record_id' do
      expect(account_class.send(:replace_account_record_id, table_name, 'account_record_id', '1')).to eq(:@account_record_id)
    end

    it 'when field is record_id' do
      expect(account_class.send(:replace_account_record_id, table_name, 'record_id', '1')).to be_nil
    end

    it 'when field is target_record_id and table account_history' do
      expect(account_class.send(:replace_account_record_id, 'account_history', 'target_record_id', '1')).to eq(:@account_record_id)
    end

    it 'when field is search_key1 and table bus_ext_events_history' do
      expect(account_class.send(:replace_account_record_id, 'bus_ext_events_history', 'search_key1', '1')).to eq(:@account_record_id)
    end

    it 'when field is search_key1 and table bus_events_history' do
      expect(account_class.send(:replace_account_record_id, 'bus_events_history', 'search_key1', '1')).to eq(:@account_record_id)
    end
  end

  describe '#replace_tenant_record_id' do
    include_context 'account'

    it 'when field is tenant_record_id' do
      account_class.instance_variable_set(:@tenant_record_id, 10)
      expect(account_class.send(:replace_tenant_record_id, table_name, 'tenant_record_id', '1')).to eq(10)
    end

    it 'when field is search_key2 and table bus_ext_events_history' do
      account_class.instance_variable_set(:@tenant_record_id, 10)
      expect(account_class.send(:replace_tenant_record_id, 'bus_ext_events_history', 'search_key2', '1')).to eq(10)
    end

    it 'when field is search_key2 and table bus_events_history' do
      account_class.instance_variable_set(:@tenant_record_id, 10)
      expect(account_class.send(:replace_tenant_record_id, 'bus_events_history', 'search_key2', '1')).to eq(10)
    end
  end

  describe '#replace_uuid' do
    include_context 'account'

    context 'when round trip true' do
      it 'when replace uuid value' do
        account_class.instance_variable_set(:@round_trip_export_import, true)
        expect(account_class.send(:replace_uuid, table_name, 'account_id', dummy_account_id)).not_to eq(dummy_account_id)
      end

      it 'when do not replace value' do
        account_class.instance_variable_set(:@round_trip_export_import, true)
        expect(account_class.send(:replace_uuid, table_name, 'other_id', dummy_account_id)).to eq(dummy_account_id)
      end
    end
  end

  describe '#sanitize' do
    include_context 'account'

    it 'when skip payment method' do
      expect(account_class.send(:sanitize, 'payment_methods', 'plugin_name', 'Payment Method', true)).to eq('__EXTERNAL_PAYMENT__')
    end
    it 'when nothing to sanitize' do
      expect(account_class.send(:sanitize, table_name, 'id', dummy_account_id, false)).to eq(dummy_account_id)
    end
  end

  describe '#sanitize_for_b64_date' do
    include_context 'account'

    let(:non_encoded_b64) do
      # This is my test data
      'This is my test data'
    end

    let(:encoded_b64) do
      # This is my test data
      'VGhpcyBpcyBteSB0ZXN0IGRhdGE='
    end

    it 'when b64 encoded data' do
      expect(account_class.send(:b64_decode_if_needed, encoded_b64).value).to start_with('LOAD_FILE("')
    end

    it 'when b64 non encoded data' do
      expect(account_class.send(:b64_decode_if_needed, non_encoded_b64)).to eq(non_encoded_b64)
    end
  end

  describe '#process_import_data' do
    include_context 'account'

    context 'when processing data to import' do
      it 'when column name qty eq column data qty without record_id' do
        account_class.instance_variable_set(:@generate_record_id, true)
        expect(account_class.send(:process_import_data, cols_data, table_name, cols_names.split('|'), false, []).size).to eq(cols_names.split('|').size - 1)
      end
    end
  end

  describe '#import_data' do
    include_context 'account'

    context 'when data to import; main import method' do
      it 'when creating test schema' do
        db = create_test_schema
        expect(db).to eq(db_name)
      end

      it 'when importing data with empty file' do
        File.new(dummy_data_file, 'w+').close
        expect { account_class.import_data(dummy_data_file, nil, true, false, true) }.to raise_error(Interrupt, "Data on #{dummy_data_file} is invalid")
        File.delete(dummy_data_file)
      end

      it 'when importing data with no file' do
        expect { account_class.import_data(dummy_data_file, nil, true, false, true) }.to raise_error(Interrupt, "File #{dummy_data_file} does not exist")
      end

      it 'when importing data with new record_id' do
        File.open(dummy_data_file, 'w') do |io|
          io.puts(dummy_data)
        end
        expect { account_class.import_data(dummy_data_file, nil, true, false, true) }.not_to raise_error

        verify_data(dummy_account_id)

        row_count_inserted = delete_statement('accounts', 'id', dummy_account_id)
        expect(row_count_inserted).to eq('1')
        row_count_inserted = delete_statement('account_history', 'external_key', dummy_account_id)
        expect(row_count_inserted).to eq('1')
      end

      it 'when importing data reusing record_id' do
        File.open(dummy_data_file, 'w') do |io|
          io.puts(dummy_data)
        end
        expect { account_class.import_data(dummy_data_file, nil, true, false, false) }.not_to raise_error

        verify_data(dummy_account_id)

        row_count_inserted = delete_statement('accounts', 'id', dummy_account_id)
        expect(row_count_inserted).to eq('1')
        row_count_inserted = delete_statement('account_history', 'external_key', dummy_account_id)
        expect(row_count_inserted).to eq('1')
      end

      it 'when importing data with different tenant_record_id' do
        File.open(dummy_data_file, 'w') do |io|
          io.puts(dummy_data)
        end
        expect { account_class.import_data(dummy_data_file, 10, true, false, true) }.not_to raise_error

        verify_data(dummy_account_id)

        row_count_inserted = delete_statement('accounts', 'id', dummy_account_id)
        expect(row_count_inserted).to eq('1')
        row_count_inserted = delete_statement('account_history', 'external_key', dummy_account_id)
        expect(row_count_inserted).to eq('1')
      end

      it 'when round trip' do
        File.open(dummy_data_file, 'w') do |io|
          io.puts(dummy_data)
        end
        expect { account_class.import_data(dummy_data_file, 10, true, true, true) }.not_to raise_error
        new_account_id = account_class.instance_variable_get(:@tables_id)

        verify_data(new_account_id['accounts_id'])

        row_count_inserted = delete_statement('accounts', 'id', new_account_id['accounts_id'])
        expect(row_count_inserted).to eq('1')
        row_count_inserted = delete_statement('account_history', 'external_key', new_account_id['accounts_id'])
        expect(row_count_inserted).to eq('1')
      end

      it 'when droping test schema' do
        response = drop_test_schema
        expect(response).to match('')
      end
    end
  end

  private

  def creating_account_with_client
    KillBillClient.url = url

    options = {
      username: killbill_user,
      password: killbill_password,
      api_key: killbill_api_key,
      api_secret: killbill_api_secret
    }

    account = KillBillClient::Model::Account.new
    account.name = 'KPM Account Test'
    account.first_name_length = 3
    account.external_key = SecureRandom.uuid
    account.currency = 'USD'
    account = account.create('kpm_account_test', 'kpm_account_test', 'kpm_account_test', options)

    account.account_id
  end

  def verify_data(account_id)
    response = `#{mysql_cli} #{db_name} -e "select company_name FROM accounts WHERE id = '#{account_id}';" 2>&1`
    response_msg = response.split("\n")
    company_name = response_msg[response_msg.size - 1]

    expect(company_name).to eq('Company|\\nName')
  end

  def delete_statement(table_name, column_name, account_id)
    response = `#{mysql_cli} #{db_name} -e "DELETE FROM #{table_name} WHERE #{column_name} = '#{account_id}'; SELECT ROW_COUNT();" 2>&1`
    response_msg = response.split("\n")
    response_msg[response_msg.size - 1]
  end

  def create_test_schema
    `#{mysql_cli} -e "CREATE DATABASE IF NOT EXISTS #{db_name};"`
    response = `#{mysql_cli} #{db_name} < "#{test_ddl}" 2>&1`
    response_msg = response.split("\n")
    response_msg[response_msg.size - 1]
  end

  def drop_test_schema
    `#{mysql_cli} -e "DROP DATABASE #{db_name};"`
  end
end
