require 'spec_helper_unit'
require_relative '../../api/json/imports_controller_shared_examples'
require_relative '../../../../app/controllers/carto/api/imports_controller'
require 'helpers/unique_names_helper'

# TODO: replace remaining instance variables with let,
# but, for the time being, let's keep rubocop quiet:
# rubocop:disable RSpec/InstanceVariable

describe Carto::Api::ImportsController do
  include UniqueNamesHelper
  it_behaves_like 'imports controllers' do
  end

  @headers = { 'CONTENT_TYPE'  => 'application/json' }

  before do
    @user = create(:valid_user)
    host! "#{@user.username}.localhost.lan"
  end

  let(:params) { { api_key: @user.api_key } }

  it 'gets a list of all pending imports' do
    Resque.inline = false
    serve_file(Rails.root.join('spec/support/data/ESP_adm.zip')) do |url|
      post api_v1_imports_create_url, params.merge(url: url, table_name: 'wadus')
    end

    get api_v1_imports_index_url, params

    response.code.should be == '200'

    response_json = JSON.parse(response.body)
    response_json.should_not be_nil
    imports = response_json['imports']
    imports.should have(1).items
    Resque.inline = true
  end

  it "doesn't return old pending imports" do
    Resque.inline = false
    serve_file(Rails.root.join('spec/support/data/ESP_adm.zip')) do |url|
      post api_v1_imports_create_url, params.merge(url: url, table_name: 'wadus')
    end

    Delorean.jump(7.hours)
    get api_v1_imports_index_url, params

    response.code.should be == '200'

    response_json = JSON.parse(response.body)
    response_json.should_not be_nil
    imports = response_json['imports']
    imports.should have(0).items
    Resque.inline = true
    Delorean.back_to_the_present
  end

  it 'gets the detail of an import' do
    post api_v1_imports_create_url(api_key: @user.api_key, table_name: 'wadus'),
         filename: upload_file('db/fake_data/column_number_to_boolean.csv', 'text/csv')

    item_queue_id = JSON.parse(response.body)['item_queue_id']

    get api_v1_imports_show_url(:id => item_queue_id), params

    response.code.should be == '200'

    import = JSON.parse(response.body)
    import['state'].should be == 'complete'
    import['display_name'].should be == 'column_number_to_boolean.csv'
  end

  it 'gets the detail of an import stuck unpacking' do
    post api_v1_imports_create_url(api_key: @user.api_key, table_name: 'wadus'),
         filename: upload_file('db/fake_data/column_number_to_boolean.csv', 'text/csv')

    response_json = JSON.parse(response.body)
    last_import = DataImport[response_json['item_queue_id']]
    last_import.state = DataImport::STATE_UNPACKING
    last_import.created_at -= 5.years
    last_import.save

    get api_v1_imports_show_url(:id => last_import.id), params

    response.code.should be == '200'

    import = JSON.parse(response.body)
    import['state'].should be == 'failure'
    import['error_code'].should be 6671
  end

  it 'tries to import a tgz' do

    post api_v1_imports_create_url,
         params.merge(filename: upload_file('spec/support/data/Weird Filename (2).tgz', 'application/octet-stream'))

    item_queue_id = JSON.parse(response.body)['item_queue_id']

    get api_v1_imports_show_url(:id => item_queue_id), params

    response.code.should be == '200'
    import = JSON.parse(response.body)
    import['state'].should be == 'complete'
    import['display_name'].should be == 'Weird_Filename_(2).tgz'
  end

  it 'fails with password protected files' do
    post api_v1_imports_create_url,
         params.merge(filename: upload_file('spec/support/data/alldata-pass.zip', 'application/octet-stream'))

    item_queue_id = JSON.parse(response.body)['item_queue_id']

    get api_v1_imports_show_url(:id => item_queue_id), params

    response.code.should be == '200'
    import = JSON.parse(response.body)
    import['state'].should be == 'failure'
  end

  it 'imports files with weird filenames' do
    post api_v1_imports_create_url,
         params.merge(filename: upload_file('spec/support/data/Weird Filename (2).csv', 'application/octet-stream'))

    item_queue_id = JSON.parse(response.body)['item_queue_id']

    get api_v1_imports_show_url(:id => item_queue_id), params

    response.code.should be == '200'
    import = JSON.parse(response.body)
    import['state'].should be == 'complete'
  end

  it 'creates a table from a sql query' do
    post api_v1_imports_create_url(params.merge(table_name: 'wadus_2')),
         filename: upload_file('spec/support/data/_penguins_below_80.zip', 'application/octet-stream')

    response.code.should be == '200'

    @table_from_import = UserTable.all.last.service
    post api_v1_imports_create_url(:api_key    => @user.api_key,
                        table_name: 'wadus_2',
                        :sql        => "SELECT * FROM #{@table_from_import.name}")


    response.code.should be == '200'

    response_json = JSON.parse(response.body)

    last_import = DataImport[response_json['item_queue_id']]
    last_import.state.should be == 'complete'

    import_table = UserTable.all.last.service
    import_table.rows_counted.should be == @table_from_import.rows_counted
    import_table.should have_required_indexes_and_triggers
  end

  it 'returns derived visualization id if created with create_vis flag' do
    @user.update private_tables_enabled: false
    post api_v1_imports_create_url,
         params.merge({filename: upload_file('spec/support/data/csv_with_lat_lon.csv', 'application/octet-stream'),
                       create_vis: true})
    response.code.should be == '200'

    item_queue_id = ::JSON.parse(response.body)['item_queue_id']

    get api_v1_imports_show_url(id: item_queue_id), params

    import = DataImport[item_queue_id]

    import.state.should be == 'complete'
    import.visualization_id.nil?.should eq false
    import.create_visualization.should eq true

    vis = CartoDB::Visualization::Member.new(id: import.visualization_id).fetch
    vis.nil?.should eq false
    vis.name =~ /csv_with_lat_lon/  # just in case we change the prefix

    @user.update private_tables_enabled: true
  end

  describe 'service_token_valid?' do
    it 'returns oauth_valid false for unknown service tokens' do
      get api_v1_imports_service_token_valid_url(id: 'kk'), params
      response.code.should == '200'
      response_json = JSON.parse(response.body)
      response_json['oauth_valid'].should == false
      response_json['success'].should == true
    end

    it 'returns oauth_valid false for not valid tokens and deletes them' do
      CartoDB::Datasources::Url::MailChimp.any_instance.stubs(:token_valid?).returns(false)
      connector = Carto::Connection.new(user_id: @user.id, connector: 'mailchimp', token: 'kk-t')
      connector.save
      get api_v1_imports_service_token_valid_url(id: connector.service), params
      response.code.should == '200'
      response_json = JSON.parse(response.body)
      response_json['oauth_valid'].should == false
      response_json['success'].should == true

      Carto::Connection.where(id: connector.id).first.should eq nil
      connector.destroy
    end

    it 'returns 401 for expired tokens on assignment and deletes them' do
      CartoDB::Datasources::Url::MailChimp.any_instance.stubs(:token=).raises(CartoDB::Datasources::TokenExpiredOrInvalidError.new('kk', 'mailchimp'))
      connector = Carto::Connection.new(user_id: @user.id, connector: 'mailchimp', token: 'kk-t')
      connector.save

      get api_v1_imports_service_token_valid_url(id: connector.service), params, @headers
      response.code.should == '401'

      Carto::Connection.where(id: connector.id).first.should eq nil
      connector.destroy
    end

    it 'returns 401 for expired tokens on validation and deletes them' do
      CartoDB::Datasources::Url::MailChimp.any_instance.stubs(:token_valid?).raises(CartoDB::Datasources::TokenExpiredOrInvalidError.new('kk', 'mailchimp'))
      connector = Carto::Connection.new(user_id: @user.id, connector: 'mailchimp', token: 'kk-t')
      connector.save

      get api_v1_imports_service_token_valid_url(id: connector.service), params, @headers
      response.code.should == '401'

      Carto::Connection.where(id: connector.id).first.should eq nil
      connector.destroy
    end

  end

  describe 'list_files_for_service' do

    def fake_item_data(datasource_name = 'fake_datasource')
      id = unique_integer
      {
        id:       id,
        title:    "title_#{id}",
        filename: "filename_#{id}",
        service:  datasource_name,
        checksum: id,
        size:     rand(50)
      }
    end

    def fake_resource_list(datasource_name = 'fake_datasource')
      [ fake_item_data(datasource_name), fake_item_data(datasource_name) ]
    end

    it 'returns datasource resources list for known, valid tokens' do
      service = 'mailchimp'
      fake_files = fake_resource_list(service)
      CartoDB::Datasources::Url::MailChimp.any_instance.stubs(:get_resources_list).returns(fake_files)
      connector = Carto::Connection.new(user_id: @user.id, connector: service, token: 'kk-t')
      connector.save
      get api_v1_imports_service_list_files_url(id: service), params
      response.code.should == '200'
      response_json = JSON.parse(response.body)
      response_json['success'].should == true
      response_json['files'].map(&:symbolize_keys).should == fake_files
      connector.destroy
    end

    it 'returns 401 for expired tokens on resource listing and deletes them' do
      CartoDB::Datasources::Url::MailChimp.any_instance.stubs(:get_resources_list).raises(CartoDB::Datasources::TokenExpiredOrInvalidError.new('kk', 'mailchimp'))
      connector = Carto::Connection.new(user_id: @user.id, connector: 'mailchimp', token: 'kk-t')
      connector.save

      get api_v1_imports_service_list_files_url(id: 'mailchimp'), params
      response.code.should == '401'

      Carto::Connection.where(id: connector.id).first.should eq nil
      connector.destroy
    end

  end

  describe 'auth_url' do

    it 'returns 400 for existing tokens services' do
      service = 'mailchimp'
      connector = Carto::Connection.new(user_id: @user.id, connector: service, token: 'kk-t')
      connector.save
      get api_v1_imports_service_auth_url_url(id: service), params
      response.code.should == '400'
      connector.destroy
    end

    it 'returns auth url for known, valid tokens' do
      service = 'mailchimp'
      fake_url = 'http://www.fakeurl.com'
      CartoDB::Datasources::Url::MailChimp.any_instance.stubs(:get_auth_url).returns(fake_url)
      get api_v1_imports_service_auth_url_url(id: service), params
      response.code.should == '200'
      response_json = JSON.parse(response.body)
      response_json['success'].should == true
      response_json['url'].should == fake_url
    end

    it 'returns 401 for expired tokens on url and deletes them' do
      CartoDB::Datasources::Url::MailChimp.any_instance.stubs(:get_auth_url).raises(CartoDB::Datasources::TokenExpiredOrInvalidError.new('kk', 'mailchimp'))

      get api_v1_imports_service_auth_url_url(id: 'mailchimp'), params
      response.code.should == '401'

      # INFO: this can never happen with the current implementation of get_service_auth_url,
      #       since it first checks there's no previous Connection
      Carto::Connection.where(connector: 'mailchimp').first.should eq nil
    end
  end

  describe 'validate_service_oauth_code' do

    it 'returns 400 for existing tokens services' do
      service = 'mailchimp'
      connector = Carto::Connection.new(user_id: @user.id, connector: service, token: 'kk-t')
      connector.save
      get api_v1_imports_service_validate_code_url(id: service, code: 'kk'), params
      response.code.should == '400'
      connector.destroy
    end

    it 'returns 400 if it does not find datasource' do
      CartoDB::Datasources::DatasourcesFactory.stubs(:get_datasource).returns(nil)
      get api_v1_imports_service_validate_code_url(id: 'kk', code: 'kk'), params
      response.code.should == '400'
    end

    it 'returns 401 for expired tokens on url and deletes them' do
      CartoDB::Datasources::DatasourcesFactory.stubs(:get_datasource).raises(CartoDB::Datasources::TokenExpiredOrInvalidError.new('kk', 'mailchimp'))

      get api_v1_imports_service_validate_code_url(id: 'mailchimp', code: 'kk'), params
      response.code.should == '401'

      # INFO: this can never happen with the current implementation since it first checks there's no previous Connection
      Carto::Connection.where(connector: 'mailchimp').first.should eq nil
    end

    it 'returns not success 200 and does not store oauth for not valid codes' do
      CartoDB::Datasources::Url::MailChimp.any_instance.stubs(:validate_auth_code).raises(CartoDB::Datasources::AuthError.new)

      get api_v1_imports_service_validate_code_url(id: 'mailchimp', code: 'kk'), params
      response.code.should == '200'
      response_json = JSON.parse(response.body)
      response_json['success'].should == false

      Carto::Connection.where(connector: 'mailchimp').first.should eq nil
    end

    it 'returns 400 if validation fails catastrophically' do
      CartoDB::Datasources::Url::MailChimp.any_instance.stubs(:validate_auth_code).raises(StandardError.new)

      get api_v1_imports_service_validate_code_url(id: 'mailchimp', code: 'kk'), params
      response.code.should == '400'

      # INFO: this can never happen with the current implementation since it first checks there's no previous Connection
      Carto::Connection.where(connector: 'mailchimp').first.should eq nil
    end

    it 'returns success 200 and stores oauth for valid codes' do
      token = 'kk'
      CartoDB::Datasources::Url::MailChimp.any_instance.stubs(:validate_auth_code).returns(token)

      get api_v1_imports_service_validate_code_url(id: 'mailchimp', code: 'kk'), params
      response.code.should == '200'
      response_json = JSON.parse(response.body)
      response_json['success'].should == true

      connector = Carto::Connection.where(connector: 'mailchimp').first
      connector.token.should eq token
      connector.user_id.should eq @user.id
      connector.destroy
    end

  end

  describe 'service_oauth_callback' do

    it 'returns 400 for existing tokens services' do
      service = 'mailchimp'
      connector = Carto::Connection.new(user_id: @user.id, connector: service, token: 'kk-t')
      connector.save
      get api_v1_imports_service_oauth_callback_url(id: service), params
      response.code.should eq '400'
      connector.destroy
    end

    it 'returns 401 for expired tokens and deletes them' do
      CartoDB::Datasources::DatasourcesFactory.stubs(:get_datasource).raises(CartoDB::Datasources::TokenExpiredOrInvalidError.new('kk', 'mailchimp'))

      get api_v1_imports_service_oauth_callback_url(id: 'mailchimp'), params
      response.code.should eq '400'

      # INFO: this can never happen with the current implementation since it first checks there's no previous Connection
      Carto::Connection.where(connector: 'mailchimp').first.should eq nil
    end

    it 'returns success 200 and stores oauth for valid params' do
      token = 'kk'
      CartoDB::Datasources::Url::MailChimp.any_instance.stubs(:validate_callback).returns(token)

      get api_v1_imports_service_oauth_callback_url(id: 'mailchimp'), params
      response.code.should == '200'
      response.body.should == '<script>window.close();</script>'

      connector = Carto::Connection.where(connector: 'mailchimp').first
      connector.token.should eq token
      connector.user_id.should eq @user.id
      connector.destroy
    end

  end

end

# rubocop:enable RSpec/InstanceVariable
