<?php
/**
 * Invoice Ninja (https://invoiceninja.com).
 *
 * @link https://github.com/invoiceninja/invoiceninja source repository
 *
 * @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
 *
 * @license https://www.elastic.co/licensing/elastic-license
 */

namespace App\Jobs\Util;

use App\DataMapper\Analytics\MigrationFailure;
use App\DataMapper\CompanySettings;
use App\Exceptions\MigrationValidatorFailed;
use App\Exceptions\ProcessingMigrationArchiveFailed;
use App\Exceptions\ResourceDependencyMissing;
use App\Exceptions\ResourceNotAvailableForMigration;
use App\Factory\ClientContactFactory;
use App\Factory\ClientFactory;
use App\Factory\CompanyLedgerFactory;
use App\Factory\CreditFactory;
use App\Factory\InvoiceFactory;
use App\Factory\PaymentFactory;
use App\Factory\ProductFactory;
use App\Factory\QuoteFactory;
use App\Factory\RecurringInvoiceFactory;
use App\Factory\TaxRateFactory;
use App\Factory\UserFactory;
use App\Factory\VendorFactory;
use App\Http\Requests\Company\UpdateCompanyRequest;
use App\Http\ValidationRules\User\AttachableUser;
use App\Http\ValidationRules\ValidCompanyGatewayFeesAndLimitsRule;
use App\Http\ValidationRules\ValidUserForCompany;
use App\Jobs\Company\CreateCompanyTaskStatuses;
use App\Jobs\Company\CreateCompanyToken;
use App\Jobs\Mail\NinjaMailerJob;
use App\Jobs\Mail\NinjaMailerObject;
use App\Jobs\Ninja\CheckCompanyData;
use App\Jobs\Ninja\CompanySizeCheck;
use App\Jobs\Util\VersionCheck;
use App\Libraries\MultiDB;
use App\Mail\MigrationCompleted;
use App\Mail\Migration\StripeConnectMigration;
use App\Models\Activity;
use App\Models\Client;
use App\Models\ClientContact;
use App\Models\ClientGatewayToken;
use App\Models\Company;
use App\Models\CompanyGateway;
use App\Models\Credit;
use App\Models\Document;
use App\Models\Expense;
use App\Models\ExpenseCategory;
use App\Models\Invoice;
use App\Models\Payment;
use App\Models\PaymentTerm;
use App\Models\Product;
use App\Models\Project;
use App\Models\Quote;
use App\Models\RecurringInvoice;
use App\Models\Task;
use App\Models\TaskStatus;
use App\Models\TaxRate;
use App\Models\User;
use App\Models\Vendor;
use App\Repositories\ClientContactRepository;
use App\Repositories\ClientRepository;
use App\Repositories\CompanyRepository;
use App\Repositories\CreditRepository;
use App\Repositories\Migration\InvoiceMigrationRepository;
use App\Repositories\Migration\PaymentMigrationRepository;
use App\Repositories\ProductRepository;
use App\Repositories\QuoteRepository;
use App\Repositories\UserRepository;
use App\Repositories\VendorContactRepository;
use App\Repositories\VendorRepository;
use App\Utils\Ninja;
use App\Utils\Traits\CleanLineItems;
use App\Utils\Traits\CompanyGatewayFeesAndLimitsSaver;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\SavesDocuments;
use App\Utils\Traits\Uploadable;
use Exception;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Http\UploadedFile;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use Turbo124\Beacon\Facades\LightLogs;

class Import implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
    use CompanyGatewayFeesAndLimitsSaver;
    use MakesHash;
    use CleanLineItems;
    use Uploadable;
    use SavesDocuments;
    /**
     * @var array
     */
    private $file_path; //the file path - using a different JSON parser here.

    /**
     * @var Company
     */
    private $company;

    private $token;

    /**
     * @var array
     */
    private $available_imports = [
        'account',
        'company',
        'users',
        'payment_terms',
        'tax_rates',
        'clients',
        'company_gateways',
        'client_gateway_tokens',
        'vendors',
        'projects',
        'products',
        'credits',
        'invoices',
        'recurring_invoices',
        'quotes',
        'payments',
        'expense_categories',
        'task_statuses',
        'expenses',
        'tasks',
        'documents',
    ];

    /**
     * @var User
     */
    private $user;

    /**
     * Custom list of resources to be imported.
     *
     * @var array
     */
    private $resources;

    /**
     * Local state manager for ids.
     *
     * @var array
     */
    private $ids = [];

    public $tries = 1;

    public $timeout = 0;

    // public $backoff = 86430;

    //  public $maxExceptions = 2;
    /**
     * Create a new job instance.
     *
     * @param array $data
     * @param Company $company
     * @param User $user
     * @param array $resources
     */
    public function __construct(string $file_path, Company $company, User $user, array $resources = [])
    {
        $this->file_path = $file_path;
        $this->company = $company;
        $this->user = $user;
        $this->resources = $resources;
    }

    /**
     * Execute the job.
     *
     * @return bool
     */
    public function handle()
    {
        set_time_limit(0);

        nlog("Starting Migration");
        nlog($this->user->email);
        nlog("Company ID = ");
        nlog($this->company->id);
        
        auth()->login($this->user, false);
        auth()->user()->setCompany($this->company);

        $array = json_decode(file_get_contents($this->file_path), 1);
        $data = $array['data'];

        foreach ($this->available_imports as $import) {
            if (! array_key_exists($import, $data)) {
                info("Resource {$import} is not available for migration.");
                continue;
            }

            $method = sprintf('process%s', Str::ucfirst(Str::camel($import)));

            info("Importing {$import}");

            $this->{$method}($data[$import]);
        }

        $task_statuses = [
            ['name' => ctrans('texts.backlog'), 'company_id' => $this->company->id, 'user_id' => $this->user->id, 'created_at' => now(), 'updated_at' => now(), 'status_order' => 1],
            ['name' => ctrans('texts.ready_to_do'), 'company_id' => $this->company->id, 'user_id' => $this->user->id, 'created_at' => now(), 'updated_at' => now(), 'status_order' => 2],
            ['name' => ctrans('texts.in_progress'), 'company_id' => $this->company->id, 'user_id' => $this->user->id, 'created_at' => now(), 'updated_at' => now(), 'status_order' => 3],
            ['name' => ctrans('texts.done'), 'company_id' => $this->company->id, 'user_id' => $this->user->id, 'created_at' => now(), 'updated_at' => now(), 'status_order' => 4],

        ];

        TaskStatus::insert($task_statuses);

        $account = $this->company->account;
        $account->default_company_id = $this->company->id;
        $account->save();

        //company size check
        if ($this->company->invoices()->count() > 1000 || $this->company->products()->count() > 1000 || $this->company->clients()->count() > 1000) {
            $this->company->is_large = true;
            $this->company->save();
        }

        $this->setInitialCompanyLedgerBalances();
        
        // $this->fixClientBalances();
        $check_data = CheckCompanyData::dispatchNow($this->company, md5(time()));
        
        // if(Ninja::isHosted() && array_key_exists('ninja_tokens', $data))
        $this->processNinjaTokens($data['ninja_tokens']);

        // $this->fixData();
        try{
            App::forgetInstance('translator');
            $t = app('translator');
            $t->replace(Ninja::transformTranslations($this->company->settings));
        
            Mail::to($this->user->email, $this->user->name())
                ->send(new MigrationCompleted($this->company, implode("<br>",$check_data)));
        }
        catch(\Exception $e) {
            nlog($e->getMessage());
        }
        
        /*After a migration first some basic jobs to ensure the system is up to date*/
        VersionCheck::dispatch();
        


        // CreateCompanyPaymentTerms::dispatchNow($sp035a66, $spaa9f78);
        // CreateCompanyTaskStatuses::dispatchNow($this->company, $this->user);

        info('Completed🚀🚀🚀🚀🚀 at '.now());

        unlink($this->file_path);
    }

    private function fixData()
    {

        $this->company->clients()->withTrashed()->where('is_deleted', 0)->cursor()->each(function ($client) {
            $total_invoice_payments = 0;
            $credit_total_applied = 0;

            foreach ($client->invoices()->where('is_deleted', false)->where('status_id', '>', 1)->get() as $invoice) {

                $total_amount = $invoice->payments()->where('is_deleted', false)->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED])->get()->sum('pivot.amount');
                $total_refund = $invoice->payments()->where('is_deleted', false)->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED])->get()->sum('pivot.refunded');

                $total_invoice_payments += ($total_amount - $total_refund);
            }

            // 10/02/21
            foreach ($client->payments as $payment) {
                $credit_total_applied += $payment->paymentables()->where('paymentable_type', App\Models\Credit::class)->get()->sum(\DB::raw('amount'));
            }

            if ($credit_total_applied < 0) {
                $total_invoice_payments += $credit_total_applied;
            } 


            if (round($total_invoice_payments, 2) != round($client->paid_to_date, 2)) {

                $client->paid_to_date = $total_invoice_payments;
                $client->save();
                
            }
        });

    }

    private function setInitialCompanyLedgerBalances()
    {
        Client::where('company_id', $this->company->id)->cursor()->each(function ($client) {

            $invoice_balances = $client->invoices->where('is_deleted', false)->where('status_id', '>', 1)->sum('balance');

            $company_ledger = CompanyLedgerFactory::create($client->company_id, $client->user_id);
            $company_ledger->client_id = $client->id;
            $company_ledger->adjustment = $invoice_balances;
            $company_ledger->notes = 'Migrated Client Balance';
            $company_ledger->balance = $invoice_balances;
            $company_ledger->activity_id = Activity::CREATE_CLIENT;
            $company_ledger->save();

            $client->company_ledger()->save($company_ledger);

            $client->balance = $invoice_balances;
            $client->save();

        });
    }

    private function processAccount(array $data) :void
    {
        if(array_key_exists('token', $data)){
            $this->token = $data['token'];
            unset($data['token']);
        }

        $account = $this->company->account;
        $account->fill($data);
        $account->save();

        //Prevent hosted users being pushed into a trial
        if(Ninja::isHosted() && $account->plan != ''){
            $account->trial_plan = '';
            $account->save();
        }
    }

    /**
     * @param array $data
     * @throws Exception
     */
    private function processCompany(array $data): void
    {
        Company::unguard();

        if (
            $data['settings']['invoice_design_id'] > 9 ||
            $data['settings']['invoice_design_id'] > "9"
        ) {
            $data['settings']['invoice_design_id'] = 1;
        }

        $data = $this->transformCompanyData($data);

        if(Ninja::isHosted()) {

            if(!MultiDB::checkDomainAvailable($data['subdomain']))
                $data['subdomain'] = MultiDB::randomSubdomainGenerator();

            if(strlen($data['subdomain']) == 0)
                $data['subdomain'] = MultiDB::randomSubdomainGenerator();

        }

        $rules = (new UpdateCompanyRequest())->rules();

        $validator = Validator::make($data, $rules);

        if ($validator->fails()) 
            throw new MigrationValidatorFailed(json_encode($validator->errors()));
        
        if (isset($data['account_id'])) 
            unset($data['account_id']);
        
        if(isset($data['version']))
            unset($data['version']);

        if (isset($data['referral_code'])) {
            $account = $this->company->account;
            $account->referral_code = $data['referral_code'];
            $account->save();

            unset($data['referral_code']);
        }

        if (isset($data['custom_fields']) && is_array($data['custom_fields'])) {
            $data['custom_fields'] = $this->parseCustomFields($data['custom_fields']);
        }

        $company_repository = new CompanyRepository();
        $company_repository->save($data, $this->company);

        if (isset($data['settings']->company_logo) && strlen($data['settings']->company_logo) > 0) {
            try {
                $tempImage = tempnam(sys_get_temp_dir(), basename($data['settings']->company_logo));
                copy($data['settings']->company_logo, $tempImage);
                $this->uploadLogo($tempImage, $this->company, $this->company);
            } catch (\Exception $e) {
            }
        }

        Company::reguard();

        /*Improve memory handling by setting everything to null when we have finished*/
        $data = null;
        $rules = null;
        $validator = null;
        $company_repository = null;
    }

    private function parseCustomFields($fields) :array
    {
        
        if(array_key_exists('account1', $fields))
            $fields['company1'] = $fields['account1'];

        if(array_key_exists('account2', $fields))
            $fields['company2'] = $fields['account2'];

        if(array_key_exists('invoice1', $fields))
            $fields['surcharge1'] = $fields['invoice1'];

        if(array_key_exists('invoice2', $fields))
            $fields['surcharge2'] = $fields['invoice2'];

        if(array_key_exists('invoice_text1', $fields))
            $fields['invoice1'] = $fields['invoice_text1'];

        if(array_key_exists('invoice_text2', $fields))
            $fields['invoice2'] = $fields['invoice_text2'];

        foreach ($fields as &$value) {
            $value = (string) $value;
        }

        return $fields;
    }

    private function transformCompanyData(array $data): array
    {

        $company_settings = CompanySettings::defaults();

        if (array_key_exists('settings', $data)) {
            foreach ($data['settings'] as $key => $value) {
                if ($key == 'invoice_design_id' || $key == 'quote_design_id' || $key == 'credit_design_id') {
                    $value = $this->encodePrimaryKey($value);

                    if(!$value)
                        $value = $this->encodePrimaryKey(1);
                    
                }

                if ($key == 'payment_terms' && $key = '') {
                    $value = -1;
                }

                $company_settings->{$key} = $value;
            }

            $data['settings'] = $company_settings;
        }


        return $data;
    }

    /**
     * @param array $data
     * @throws Exception
     */
    private function processTaxRates(array $data): void
    {
        TaxRate::unguard();

        $rules = [
            '*.name' => 'required',
            //'*.name' => 'required|distinct|unique:tax_rates,name,null,null,company_id,' . $this->company->id,
            '*.rate' => 'required|numeric',
        ];

        $validator = Validator::make($data, $rules);

        if ($validator->fails()) {
            throw new MigrationValidatorFailed(json_encode($validator->errors()));
        }

        foreach ($data as $resource) {
            $modified = $resource;
            $company_id = $this->company->id;
            $user_id = $this->processUserId($resource);

            if (isset($resource['user_id'])) {
                unset($resource['user_id']);
            }

            if (isset($resource['company_id'])) {
                unset($resource['company_id']);
            }

            $tax_rate = TaxRateFactory::create($this->company->id, $user_id);
            $tax_rate->fill($resource);

            $tax_rate->save();
        }

        TaxRate::reguard();

        /*Improve memory handling by setting everything to null when we have finished*/
        $data = null;
        $rules = null;
        $validator = null;
    }

    /**
     * @param array $data
     * @throws Exception
     */
    private function processUsers(array $data): void
    {
        User::unguard();

        $rules = [
            '*.first_name' => ['string'],
            '*.last_name' => ['string'],
            //'*.email' => ['distinct'],
            '*.email' => ['distinct', 'email', new ValidUserForCompany()],
        ];

        $validator = Validator::make($data, $rules);

        if ($validator->fails()) {
            throw new MigrationValidatorFailed(json_encode($validator->errors()));
        }

        $user_repository = new UserRepository();

        foreach ($data as $resource) {
            $modified = $resource;
            unset($modified['id']);
            unset($modified['password']); //cant import passwords.
            unset($modified['confirmation_code']); //cant import passwords.

            $user = $user_repository->save($modified, $this->fetchUser($resource['email']), true, true);
            $user->email_verified_at = now();
            // $user->confirmation_code = '';

            if($modified['deleted_at'])
                $user->deleted_at = now();
            
            $user->save();
            
            $user_agent = array_key_exists('token_name', $resource) ?: request()->server('HTTP_USER_AGENT');

            CreateCompanyToken::dispatchNow($this->company, $user, $user_agent);

            $key = "users_{$resource['id']}";

            $this->ids['users'][$key] = [
                'old' => $resource['id'],
                'new' => $user->id,
            ];
        }

        User::reguard();

        /*Improve memory handling by setting everything to null when we have finished*/
        $data = null;
        $rules = null;
        $validator = null;
        $user_repository = null;
    }

    private function checkUniqueConstraint($model, $column, $value)
    {
        $value = trim($value);

        $model_query = $model::where($column, $value)
                             ->where('company_id', $this->company->id)
                             ->withTrashed()
                             ->exists();

        if($model_query)
            return $value . '_' . Str::random(5);

        return $value;
    }

    /**
     * @param array $data
     * @throws Exception
     */
    private function processClients(array $data): void
    {
        Client::unguard();

        $contact_repository = new ClientContactRepository();
        $client_repository = new ClientRepository($contact_repository);

        foreach ($data as $key => $resource) {
            $modified = $resource;
            $modified['company_id'] = $this->company->id;
            $modified['user_id'] = $this->processUserId($resource);
            $modified['balance'] = $modified['balance'] ?: 0;
            $modified['paid_to_date'] = $modified['paid_to_date'] ?: 0;
            $modified['number'] = $this->checkUniqueConstraint(Client::class, 'number', $modified['number']);

            unset($modified['id']);
            unset($modified['contacts']);

            $client = $client_repository->save(
                $modified,
                ClientFactory::create(
                    $this->company->id,
                    $modified['user_id']
                )
            );

            if(array_key_exists('created_at', $modified))
                $client->created_at = Carbon::parse($modified['created_at']);

            if(array_key_exists('updated_at', $modified))
                $client->updated_at = Carbon::parse($modified['updated_at']);

            $client->save(['timestamps' => false]);

            $client->contacts()->forceDelete();

            if (array_key_exists('contacts', $resource)) { // need to remove after importing new migration.json
                $modified_contacts = $resource['contacts'];

                foreach ($modified_contacts as $key => $client_contacts) {
                    $modified_contacts[$key]['company_id'] = $this->company->id;
                    $modified_contacts[$key]['user_id'] = $this->processUserId($resource);
                    $modified_contacts[$key]['client_id'] = $client->id;
                    $modified_contacts[$key]['password'] = 'mysuperpassword'; // @todo, and clean up the code..
                    unset($modified_contacts[$key]['id']);
                }

                $saveable_contacts['contacts'] = $modified_contacts;

                $contact_repository->save($saveable_contacts, $client);

                //link contact ids

                foreach ($resource['contacts'] as $key => $old_contact) {
                    
                    $contact_match = ClientContact::where('contact_key', $old_contact['contact_key'])
                                                 ->where('company_id', $this->company->id)
                                                 ->where('client_id', $client->id)
                                                 ->withTrashed()
                                                 ->first();

                    if ($contact_match) {
                        
                        $this->ids['client_contacts']['client_contacts_'.$old_contact['id']] = [
                            'old' => $old_contact['id'],
                            'new' => $contact_match->id,
                        ];
                        
                    }
                }
            }

            $key = "clients_{$resource['id']}";

            $this->ids['clients'][$key] = [
                'old' => $resource['id'],
                'new' => $client->id,
            ];
        }

        Client::reguard();

        /*Improve memory handling by setting everything to null when we have finished*/
        $data = null;
        $contact_repository = null;
        $client_repository = null;
    }

    /**
     * @param array $data
     * @throws Exception
     */
    private function processVendors(array $data): void
    {
        Vendor::unguard();

        $contact_repository = new VendorContactRepository();
        $vendor_repository = new VendorRepository($contact_repository);

        foreach ($data as $key => $resource) {
            $modified = $resource;
            $modified['company_id'] = $this->company->id;
            $modified['user_id'] = $this->processUserId($resource);

            unset($modified['id']);
            unset($modified['contacts']);

            if(array_key_exists('created_at', $modified))
                $modified['created_at'] = Carbon::parse($modified['created_at']);

            if(array_key_exists('updated_at', $modified))
                $modified['updated_at'] = Carbon::parse($modified['updated_at']);

            $vendor = $vendor_repository->save(
                $modified,
                VendorFactory::create(
                    $this->company->id,
                    $modified['user_id']
                )
            );

            $vendor->contacts()->forceDelete();

            if (array_key_exists('contacts', $resource)) { // need to remove after importing new migration.json
                $modified_contacts = $resource['contacts'];

                foreach ($modified_contacts as $key => $vendor_contacts) {
                    $modified_contacts[$key]['company_id'] = $this->company->id;
                    $modified_contacts[$key]['user_id'] = $this->processUserId($resource);
                    $modified_contacts[$key]['vendor_id'] = $vendor->id;
                    $modified_contacts[$key]['password'] = 'mysuperpassword'; // @todo, and clean up the code..
                    unset($modified_contacts[$key]['id']);
                }

                $saveable_contacts['contacts'] = $modified_contacts;

                $contact_repository->save($saveable_contacts, $vendor);
            }

            $key = "vendors_{$resource['id']}";

            $this->ids['vendors'][$key] = [
                'old' => $resource['id'],
                'new' => $vendor->id,
            ];
        }

        Vendor::reguard();

        /*Improve memory handling by setting everything to null when we have finished*/
        $data = null;
        $contact_repository = null;
        $client_repository = null;
    }


    private function processProducts(array $data): void
    {
        Product::unguard();

        $rules = [
            //'*.product_key' => 'required|distinct|unique:products,product_key,null,null,company_id,' . $this->company->id,
            '*.cost' => 'numeric',
            '*.price' => 'numeric',
            '*.quantity' => 'numeric',
        ];

        $validator = Validator::make($data, $rules);

        if ($validator->fails()) {
            throw new MigrationValidatorFailed(json_encode($validator->errors()));
        }

        $product_repository = new ProductRepository();

        foreach ($data as $resource) {
            $modified = $resource;
            $modified['company_id'] = $this->company->id;
            $modified['user_id'] = $this->processUserId($resource);

            if(array_key_exists('created_at', $modified))
                $modified['created_at'] = Carbon::parse($modified['created_at']);

            if(array_key_exists('updated_at', $modified))
                $modified['updated_at'] = Carbon::parse($modified['updated_at']);

            unset($modified['id']);

            $product_repository->save(
                $modified,
                ProductFactory::create(
                    $this->company->id,
                    $modified['user_id']
                )
            );
        }

        Product::reguard();

        /*Improve memory handling by setting everything to null when we have finished*/
        $data = null;
        $product_repository = null;
    }

    private function processRecurringInvoices(array $data) :void
    {
        RecurringInvoice::unguard();

        $rules = [
            '*.client_id' => ['required'],
        ];

        $validator = Validator::make($data, $rules);

        if ($validator->fails()) {
            throw new MigrationValidatorFailed(json_encode($validator->errors()));
        }

        $invoice_repository = new InvoiceMigrationRepository();

        foreach ($data as $key => $resource) {
            $modified = $resource;

            if (array_key_exists('client_id', $resource) && ! array_key_exists('clients', $this->ids)) {
                throw new ResourceDependencyMissing('Processing invoices failed, because of missing dependency - clients.');
            }

            $modified['client_id'] = $this->transformId('clients', $resource['client_id']);
            $modified['user_id'] = $this->processUserId($resource);
            $modified['company_id'] = $this->company->id;
            $modified['line_items'] = $this->cleanItems($modified['line_items']);

            if(array_key_exists('created_at', $modified))
                $modified['created_at'] = Carbon::parse($modified['created_at']);

            if(array_key_exists('updated_at', $modified))
                $modified['updated_at'] = Carbon::parse($modified['updated_at']);

            unset($modified['id']);

            if (array_key_exists('invitations', $resource)) {
                foreach ($resource['invitations'] as $key => $invite) {
                    $resource['invitations'][$key]['client_contact_id'] = $this->transformId('client_contacts', $invite['client_contact_id']);
                    $resource['invitations'][$key]['user_id'] = $modified['user_id'];
                    $resource['invitations'][$key]['company_id'] = $this->company->id;
                    $resource['invitations'][$key]['email_status'] = '';

                    unset($resource['invitations'][$key]['recurring_invoice_id']);
                    unset($resource['invitations'][$key]['id']);

                }
            
                $modified['invitations'] = $this->deDuplicateInvitations($resource['invitations']);

            }
            
            $invoice = $invoice_repository->save(
                $modified,
                RecurringInvoiceFactory::create($this->company->id, $modified['user_id'])
            );

            $key = "recurring_invoices_{$resource['id']}";

            $this->ids['recurring_invoices'][$key] = [
                'old' => $resource['id'],
                'new' => $invoice->id,
            ];
        }

        RecurringInvoice::reguard();

        /*Improve memory handling by setting everything to null when we have finished*/
        $data = null;
        $invoice_repository = null;
    }

    private function processInvoices(array $data): void
    {
        Invoice::unguard();

        $rules = [
            '*.client_id' => ['required'],
        ];

        $validator = Validator::make($data, $rules);

        if ($validator->fails()) {
            throw new MigrationValidatorFailed(json_encode($validator->errors()));
        }

        $invoice_repository = new InvoiceMigrationRepository();

        foreach ($data as $key => $resource) {
            $modified = $resource;

            if (array_key_exists('client_id', $resource) && ! array_key_exists('clients', $this->ids)) {
                throw new ResourceDependencyMissing('Processing invoices failed, because of missing dependency - clients.');
            }

            $modified['client_id'] = $this->transformId('clients', $resource['client_id']);
            $modified['user_id'] = $this->processUserId($resource);
            $modified['company_id'] = $this->company->id;
            $modified['line_items'] = $this->cleanItems($modified['line_items']);

            unset($modified['id']);
                
            if (array_key_exists('invitations', $resource)) {
                foreach ($resource['invitations'] as $key => $invite) {
                    $resource['invitations'][$key]['client_contact_id'] = $this->transformId('client_contacts', $invite['client_contact_id']);
                    $resource['invitations'][$key]['user_id'] = $modified['user_id'];
                    $resource['invitations'][$key]['company_id'] = $this->company->id;
                    $resource['invitations'][$key]['email_status'] = '';
                    unset($resource['invitations'][$key]['invoice_id']);
                    unset($resource['invitations'][$key]['id']);
                }

                $modified['invitations'] = $this->deDuplicateInvitations($resource['invitations']);

            }

            $invoice = $invoice_repository->save(
                $modified,
                InvoiceFactory::create($this->company->id, $modified['user_id'])
            );

            $key = "invoices_{$resource['id']}";

            $this->ids['invoices'][$key] = [
                'old' => $resource['id'],
                'new' => $invoice->id,
            ];
        }

        Invoice::reguard();

        /*Improve memory handling by setting everything to null when we have finished*/
        $data = null;
        $invoice_repository = null;
    }


    /* Prevent edge case where V4 has inserted multiple invitations for a resource for a client contact */
    private function deDuplicateInvitations($invitations)
    {        
        return  array_intersect_key($invitations, array_unique(array_column($invitations, 'client_contact_id')));
    }

    private function processCredits(array $data): void
    {
        Credit::unguard();

        $rules = [
            '*.client_id' => ['required'],
        ];

        $validator = Validator::make($data, $rules);

        if ($validator->fails()) {
            throw new MigrationValidatorFailed(json_encode($validator->errors()));
        }

        $credit_repository = new CreditRepository();

        foreach ($data as $resource) {
            $modified = $resource;

            if (array_key_exists('client_id', $resource) && ! array_key_exists('clients', $this->ids)) {
                throw new ResourceDependencyMissing('Processing credits failed, because of missing dependency - clients.');
            }

            $modified['client_id'] = $this->transformId('clients', $resource['client_id']);
            $modified['user_id'] = $this->processUserId($resource);
            $modified['company_id'] = $this->company->id;

            if(array_key_exists('created_at', $modified))
                $modified['created_at'] = Carbon::parse($modified['created_at']);

            if(array_key_exists('updated_at', $modified))
                $modified['updated_at'] = Carbon::parse($modified['updated_at']);

            unset($modified['id']);

            $credit = $credit_repository->save(
                $modified,
                CreditFactory::create($this->company->id, $modified['user_id'])
            );

            //remove credit balance from ledger
            if($credit->balance > 0 && $credit->client->balance > 0){
                $client = $credit->client;
                $client->balance -= $credit->balance;
                $client->save();
            }


            $key = "credits_{$resource['id']}";

            $this->ids['credits'][$key] = [
                'old' => $resource['id'],
                'new' => $credit->id,
            ];
        }

        Credit::reguard();

        /*Improve memory handling by setting everything to null when we have finished*/
        $data = null;
        $credit_repository = null;

    }

    private function processQuotes(array $data): void
    {
        Quote::unguard();

        $rules = [
            '*.client_id' => ['required'],
        ];

        $validator = Validator::make($data, $rules);

        if ($validator->fails()) {
            throw new MigrationValidatorFailed(json_encode($validator->errors()));
        }

        $quote_repository = new QuoteRepository();

        foreach ($data as $resource) {
            $modified = $resource;

            if (array_key_exists('client_id', $resource) && ! array_key_exists('clients', $this->ids)) {
                throw new ResourceDependencyMissing('Processing quotes failed, because of missing dependency - clients.');
            }

            $modified['client_id'] = $this->transformId('clients', $resource['client_id']);

            if(array_key_exists('invoice_id', $resource) && isset($resource['invoice_id']) && $this->tryTransformingId('invoices', $resource['invoice_id']))
                $modified['invoice_id'] = $this->transformId('invoices', $resource['invoice_id']);

            $modified['user_id'] = $this->processUserId($resource);

            $modified['company_id'] = $this->company->id;

            if(array_key_exists('created_at', $modified))
                $modified['created_at'] = Carbon::parse($modified['created_at']);

            if(array_key_exists('updated_at', $modified))
                $modified['updated_at'] = Carbon::parse($modified['updated_at']);
            
            if(array_key_exists('tax_rate1', $modified) && is_null($modified['tax_rate1']))
                $modified['tax_rate1'] = 0;

            if(array_key_exists('tax_rate2', $modified) && is_null($modified['tax_rate2']))
                $modified['tax_rate2'] = 0;

            unset($modified['id']);


            if (array_key_exists('invitations', $resource)) {
                foreach ($resource['invitations'] as $key => $invite) {
                    $resource['invitations'][$key]['client_contact_id'] = $this->transformId('client_contacts', $invite['client_contact_id']);
                    $resource['invitations'][$key]['user_id'] = $modified['user_id'];
                    $resource['invitations'][$key]['company_id'] = $this->company->id;
                    $resource['invitations'][$key]['email_status'] = '';
                    unset($resource['invitations'][$key]['invoice_id']);
                    unset($resource['invitations'][$key]['id']);
                }

                $modified['invitations'] = $this->deDuplicateInvitations($resource['invitations']);

            }

            $quote = $quote_repository->save(
                $modified,
                QuoteFactory::create($this->company->id, $modified['user_id'])
            );

            if(array_key_exists('created_at', $modified))
                $quote->created_at = $modified['created_at'];

            if(array_key_exists('updated_at', $modified))
                $quote->updated_at = $modified['updated_at'];

            $quote->save(['timestamps' => false]);

            $old_user_key = array_key_exists('user_id', $resource) ?? $this->user->id;

            $key = "quotes_{$resource['id']}";

            $this->ids['quotes'][$key] = [
                'old' => $resource['id'],
                'new' => $quote->id,
            ];
        }

        Quote::reguard();

        /*Improve memory handling by setting everything to null when we have finished*/
        $data = null;
        $quote_repository = null;
    }

    private function processPayments(array $data): void
    {
        Payment::reguard();

        $rules = [
            '*.amount' => ['required'],
            '*.client_id' => ['required'],
        ];

        $validator = Validator::make($data, $rules);

        if ($validator->fails()) {
            throw new MigrationValidatorFailed(json_encode($validator->errors()));
        }

        $payment_repository = new PaymentMigrationRepository(new CreditRepository());

        foreach ($data as $resource) {
            $modified = $resource;

            if (array_key_exists('client_id', $resource) && ! array_key_exists('clients', $this->ids)) {
                throw new ResourceDependencyMissing('Processing payments failed, because of missing dependency - clients.');
            }

            $modified['client_id'] = $this->transformId('clients', $resource['client_id']);
            $modified['user_id'] = $this->processUserId($resource);
            $modified['company_id'] = $this->company->id;

            unset($modified['invoice_id']);

            if (isset($modified['invoices'])) {
                foreach ($modified['invoices'] as $key => $invoice) {
                    if ($this->tryTransformingId('invoices', $invoice['invoice_id'])) {
                        $modified['invoices'][$key]['invoice_id'] = $this->transformId('invoices', $invoice['invoice_id']);
                    } else {
                       nlog($modified['invoices']);
                       unset($modified['invoices']);
                       //if the transformation didn't work - you _must_ unset this data as it will be incorrect!
                    }
                }
            }

            $payment = $payment_repository->save(
                $modified,
                PaymentFactory::create($this->company->id, $modified['user_id'])
            );

            if(array_key_exists('created_at', $modified))
                $payment->created_at = Carbon::parse($modified['created_at']);

            if(array_key_exists('updated_at', $modified))
                $payment->updated_at = Carbon::parse($modified['updated_at']);

            $payment->save(['timestamps' => false]);

            if (array_key_exists('company_gateway_id', $resource) && isset($resource['company_gateway_id']) && $resource['company_gateway_id'] != 'NULL') {

                if($this->tryTransformingId('company_gateways', $resource['company_gateway_id']))
                    $payment->company_gateway_id = $this->transformId('company_gateways', $resource['company_gateway_id']);
                
                $payment->save();
            }
            

            $old_user_key = array_key_exists('user_id', $resource) ?? $this->user->id;

            $this->ids['payments'] = [
                "payments_{$old_user_key}" => [
                    'old' => $old_user_key,
                    'new' => $payment->id,
                ],
            ];

            if(in_array($payment->status_id, [Payment::STATUS_REFUNDED, Payment::STATUS_PARTIALLY_REFUNDED])) {
                $this->processPaymentRefund($payment);
            }


        }

        Payment::reguard();

        /*Improve memory handling by setting everything to null when we have finished*/
        $data = null;
        $payment_repository = null;
    }

    private function processPaymentRefund($payment)
    {
        $invoices = $payment->invoices()->get();

        $invoices->each(function ($invoice) use($payment) {
            
            if ($payment->refunded > 0 && in_array($invoice->status_id, [Invoice::STATUS_SENT])) {

                $invoice->service()
                        ->updateBalance($payment->refunded)
                        ->updatePaidToDate($payment->refunded*-1)
                        ->updateStatus()
                        ->save();
            }

        });
    }

    private function updatePaymentForStatus($payment, $status_id) :Payment
    {
        // define('PAYMENT_STATUS_PENDING', 1);
        // define('PAYMENT_STATUS_VOIDED', 2);
        // define('PAYMENT_STATUS_FAILED', 3);
        // define('PAYMENT_STATUS_COMPLETED', 4);
        // define('PAYMENT_STATUS_PARTIALLY_REFUNDED', 5);
        // define('PAYMENT_STATUS_REFUNDED', 6);
            
        switch ($status_id) {
            case 1:
                return $payment;
                break;
            case 2:
                return $payment->service()->deletePayment();
                break;
            case 3:
                return $payment->service()->deletePayment();
                break;
            case 4:
                return $payment;
                break;
            case 5:
                $payment->status_id = Payment::STATUS_PARTIALLY_REFUNDED;
                $payment->save();
                return $payment;
                break;
            case 6:
                $payment->status_id = Payment::STATUS_REFUNDED;
                $payment->save();
                return $payment;
                break;

            default:
                return $payment;
                break;
        }
    }

    private function processDocuments(array $data): void
    {
        // Document::unguard();
        /* No validators since data provided by database is already valid. */

        foreach ($data as $resource) {

            $modified = $resource;

            if (array_key_exists('invoice_id', $resource) && $resource['invoice_id'] && ! array_key_exists('invoices', $this->ids)) {
                return;
                //throw new ResourceDependencyMissing('Processing documents failed, because of missing dependency - invoices.');
            }

            if (array_key_exists('expense_id', $resource) && $resource['expense_id'] && ! array_key_exists('expenses', $this->ids)) {
                return;
                //throw new ResourceDependencyMissing('Processing documents failed, because of missing dependency - expenses.');
            }

            if (array_key_exists('invoice_id', $resource) && $resource['invoice_id'] && array_key_exists('invoices', $this->ids)) {

                $try_quote = false;
                $exception = false;
                $entity = false;
                
                try{
                    $invoice_id = $this->transformId('invoices', $resource['invoice_id']);
                    $entity = Invoice::where('id', $invoice_id)->withTrashed()->first();
                }
                catch(\Exception $e){
                    nlog("i couldn't find the invoice document {$resource['invoice_id']}, perhaps it is a quote?");
                    nlog($e->getMessage());

                    $try_quote = true;
                }

                if($try_quote && array_key_exists('quotes', $this->ids) ) {
                    
                    try{
                        $quote_id = $this->transformId('quotes', $resource['invoice_id']);
                        $entity = Quote::where('id', $quote_id)->withTrashed()->first();
                    }
                    catch(\Exception $e){
                        nlog("i couldn't find the quote document {$resource['invoice_id']}, perhaps it is a quote?");
                        nlog($e->getMessage());
                    }
                }
                
                if(!$entity)
                    continue;
                    // throw new Exception("Resource invoice/quote document not available.");


            }


            if (array_key_exists('expense_id', $resource) && $resource['expense_id'] && array_key_exists('expenses', $this->ids)) {
                $expense_id = $this->transformId('expenses', $resource['expense_id']);
                $entity = Expense::where('id', $expense_id)->withTrashed()->first();
            }

            $file_url = $resource['url'];
            $file_name = $resource['name'];
            $file_path = sys_get_temp_dir().'/'.$file_name;

            try {
                file_put_contents($file_path, $this->curlGet($file_url));
                $finfo = new \finfo(FILEINFO_MIME_TYPE);
                $file_info = $finfo->file($file_path);

                $uploaded_file = new UploadedFile(
                                $file_path,
                                $file_name,
                                $file_info,
                                filesize($file_path),
                                0,
                                false
                            );

                $this->saveDocument($uploaded_file, $entity, $is_public = true);
            }
            catch(\Exception $e) {

                //do nothing, gracefully :)
                
            }

        }

    }

    private function processPaymentTerms(array $data) :void
    {
        PaymentTerm::unguard();

        $modified = collect($data)->map(function ($item) {
            $item['user_id'] = $this->user->id;
            $item['company_id'] = $this->company->id;
            $item['is_deleted'] = isset($item['is_deleted']) ? $item['is_deleted'] : 0;
            
            return $item;
        })->toArray();

        PaymentTerm::insert($modified);

        PaymentTerm::reguard();

        /*Improve memory handling by setting everything to null when we have finished*/
        $data = null;
    }

    private function processCompanyGateways(array $data) :void
    {
        CompanyGateway::unguard();

        $rules = [
            '*.gateway_key' => 'required',
            '*.fees_and_limits' => new ValidCompanyGatewayFeesAndLimitsRule(),
        ];

        $validator = Validator::make($data, $rules);

        if ($validator->fails()) {
            throw new MigrationValidatorFailed(json_encode($validator->errors()));
        }

        foreach ($data as $resource) {
            $modified = $resource;

            $modified['user_id'] = $this->processUserId($resource);
            $modified['company_id'] = $this->company->id;

            unset($modified['id']);

            if (isset($modified['config'])) {
                $modified['config'] = encrypt($modified['config']);
            }

            if (isset($modified['fees_and_limits'])) {
                $modified['fees_and_limits'] = $this->cleanFeesAndLimits($modified['fees_and_limits']);
            }

            /* On Hosted platform we need to advise Stripe users to connect with Stripe Connect */
            if(Ninja::isHosted() && $modified['gateway_key'] == 'd14dd26a37cecc30fdd65700bfb55b23'){

                $nmo = new NinjaMailerObject;
                $nmo->mailable = new StripeConnectMigration($this->company);
                $nmo->company = $this->company;
                $nmo->settings = $this->company->settings;
                $nmo->to_user = $this->user;
                NinjaMailerJob::dispatch($nmo, true);

                $modified['gateway_key'] = 'd14dd26a47cecc30fdd65700bfb67b34';
                
                //why do we set this to a blank array?
                //$modified['fees_and_limits'] = [];

            }

            $company_gateway = CompanyGateway::create($modified);

            $key = "company_gateways_{$resource['id']}";

            $this->ids['company_gateways'][$key] = [
                    'old' => $resource['id'],
                    'new' => $company_gateway->id,
            ];
        }

        CompanyGateway::reguard();

        /*Improve memory handling by setting everything to null when we have finished*/
        $data = null;
    }

    private function processClientGatewayTokens(array $data) :void
    {
        ClientGatewayToken::unguard();

        foreach ($data as $resource) {
            $modified = $resource;

            unset($modified['id']);

            $modified['company_id'] = $this->company->id;
            $modified['client_id'] = $this->transformId('clients', $resource['client_id']);
            $modified['company_gateway_id'] = $this->transformId('company_gateways', $resource['company_gateway_id']);
            
            //$modified['user_id'] = $this->processUserId($resource);

            $cgt = ClientGatewayToken::Create($modified);

            $key = "client_gateway_tokens_{$resource['id']}";

            $this->ids['client_gateway_tokens'][$key] = [
                'old' => $resource['id'],
                'new' => $cgt->id,
            ];
        }

        ClientGatewayToken::reguard();

        /*Improve memory handling by setting everything to null when we have finished*/
        $data = null;
    }

    private function processTaskStatuses(array $data) :void
    {
        info('in task statuses');
        TaskStatus::unguard();

        foreach ($data as $resource) {
            $modified = $resource;

            unset($modified['id']);

            $modified['company_id'] = $this->company->id;
            $modified['user_id'] = $this->processUserId($resource);

            $task_status = TaskStatus::Create($modified);

            $key = "task_statuses_{$resource['id']}";

            $this->ids['task_statuses'][$key] = [
                'old' => $resource['id'],
                'new' => $task_status->id,
            ];
        }

        TaskStatus::reguard();

        $data = null;
        info('finished task statuses');
    }

    private function processExpenseCategories(array $data) :void
    {
        ExpenseCategory::unguard();

        foreach ($data as $resource) {
            $modified = $resource;

            unset($modified['id']);

            $modified['company_id'] = $this->company->id;
            $modified['user_id'] = $this->processUserId($resource);

            $expense_category = ExpenseCategory::Create($modified);

            $old_user_key = array_key_exists('user_id', $resource) ?? $this->user->id;

            $key = "expense_categories_{$resource['id']}";

            $this->ids['expense_categories'][$key] = [
                'old' => $resource['id'],
                'new' => $expense_category->id,
            ];

            // $this->ids['expense_categories'] = [
            //     "expense_categories_{$old_user_key}" => [
            //         'old' => $resource['id'],
            //         'new' => $expense_category->id,
            //     ],
            // ];
        }
        
        ExpenseCategory::reguard();

        $data = null;
    }

    private function processTasks(array $data) :void
    {
        Task::unguard();

        foreach ($data as $resource) {
            $modified = $resource;

            unset($modified['id']);

            $modified['company_id'] = $this->company->id;
            $modified['user_id'] = $this->processUserId($resource);

            if (isset($modified['client_id'])) {
                $modified['client_id'] = $this->transformId('clients', $resource['client_id']);
            }

            if (isset($modified['invoice_id'])) {
                $modified['invoice_id'] = $this->transformId('invoices', $resource['invoice_id']);
            }
            
            if (isset($modified['project_id'])) {
                $modified['project_id'] = $this->transformId('projects', $resource['project_id']);
            }
            
            if (isset($modified['status_id'])) {
                $modified['status_id'] = $this->transformId('task_statuses', $resource['status_id']);
            }

            $task = Task::Create($modified);

            if(array_key_exists('created_at', $modified))
                $task->created_at = Carbon::parse($modified['created_at']);

            if(array_key_exists('updated_at', $modified))
                $task->updated_at = Carbon::parse($modified['updated_at']);



            $task->save(['timestamps' => false]);

            $old_user_key = array_key_exists('user_id', $resource) ?? $this->user->id;

            $this->ids['tasks'] = [
                "tasks_{$old_user_key}" => [
                    'old' => $resource['id'],
                    'new' => $task->id,
                ],
            ];
        }
        
        Task::reguard();

        $data = null;
    }

    private function processProjects(array $data) :void
    {
        Project::unguard();

        foreach ($data as $resource) {
            $modified = $resource;

            unset($modified['id']);

            $modified['company_id'] = $this->company->id;
            $modified['user_id'] = $this->processUserId($resource);

            if (isset($modified['client_id'])) {
                $modified['client_id'] = $this->transformId('clients', $resource['client_id']);
            }

            $project = Project::Create($modified);

            $key = "projects_{$resource['id']}";

            $this->ids['projects'][$key] = [
                'old' => $resource['id'],
                'new' => $project->id,
            ];
        }

        Project::reguard();

        $data = null;
    }

    private function processExpenses(array $data) :void
    {
        Expense::unguard();

        foreach ($data as $resource) {
            $modified = $resource;

            unset($modified['id']);

            $modified['company_id'] = $this->company->id;
            $modified['user_id'] = $this->processUserId($resource);

            if (isset($resource['client_id'])) {
                $modified['client_id'] = $this->transformId('clients', $resource['client_id']);
            }
            
            if (isset($resource['category_id'])) {
                $modified['category_id'] = $this->transformId('expense_categories', $resource['category_id']);
            }
            
            if (isset($resource['invoice_id'])) {
                $modified['invoice_id'] = $this->transformId('invoices', $resource['invoice_id']);
            }

            if (isset($resource['project_id'])) {
                $modified['project_id'] = $this->transformId('projects', $resource['project_id']);
            }

            if (isset($resource['vendor_id'])) {
                $modified['vendor_id'] = $this->transformId('vendors', $resource['vendor_id']);
            }

            $expense = Expense::Create($modified);

            if(array_key_exists('created_at', $modified))
                $expense->created_at = Carbon::parse($modified['created_at']);

            if(array_key_exists('updated_at', $modified))
                $expense->updated_at = Carbon::parse($modified['updated_at']);



            $expense->save(['timestamps' => false]);
            
            $old_user_key = array_key_exists('user_id', $resource) ?? $this->user->id;

            $key = "expenses_{$resource['id']}";

            $this->ids['expenses'][$key] = [
                'old' => $resource['id'],
                'new' => $expense->id,
            ];

        }

        Expense::reguard();

        $data = null;
    }
    /**
     * |--------------------------------------------------------------------------
     * | Additional migration methods.
     * |--------------------------------------------------------------------------
     * |
     * | These methods aren't initialized automatically, so they don't depend on
     * | the migration data.
     */

    /**
     * Cloned from App\Http\Requests\User\StoreUserRequest.
     *
     * @param string $data
     * @return User
     */
    public function fetchUser(string $data): User
    {
        $user = MultiDB::hasUser(['email' => $data]);

        if (! $user) {
            $user = UserFactory::create($this->company->account->id);
        }
        
        return $user;
    }

    /**
     * @param string $resource
     * @param string $old
     * @return int
     * @throws Exception
     */
    public function transformId($resource, string $old): int
    {

        if (! array_key_exists($resource, $this->ids)) {
            info(print_r($resource, 1));
            throw new Exception("Resource {$resource} not available.");
        }

        if (! array_key_exists("{$resource}_{$old}", $this->ids[$resource])) {
            throw new Exception("Missing resource key: {$resource}_{$old}");
        }

        return $this->ids[$resource]["{$resource}_{$old}"]['new'];
    }

    private function tryTransformingId($resource, string $old): ?int
    {
        if (! array_key_exists($resource, $this->ids)) {
            return false;
        }

        if (! array_key_exists("{$resource}_{$old}", $this->ids[$resource])) {
            return false;
        }

        return $this->ids[$resource]["{$resource}_{$old}"]['new'];
    }

    /**
     * Process & handle user_id.
     *
     * @param array $resource
     * @return int|mixed
     * @throws Exception
     */
    public function processUserId(array $resource)
    {
        if (! array_key_exists('user_id', $resource)) {
            return $this->user->id;
        }

        if (array_key_exists('user_id', $resource) && ! array_key_exists('users', $this->ids)) {
            return $this->user->id;
        }

        return $this->transformId('users', $resource['user_id']);
    }

    public function failed($exception = null)
    {
        info('the job failed');

        $job_failure = new MigrationFailure();
        $job_failure->string_metric5 = get_class($this);
        $job_failure->string_metric6 = $exception->getMessage();

        LightLogs::create($job_failure)
                 ->batch();

        info(print_r($exception->getMessage(), 1));

        if(Ninja::isHosted())
            app('sentry')->captureException($exception);
    }


    public function curlGet($url, $headers = false)
    {

        return $this->exec('GET', $url, null);
    }

    public function exec($method, $url, $data)
    {

        $client =  new \GuzzleHttp\Client(['headers' => 
            [ 
            'X-Ninja-Token' => $this->token,        
            ]
        ]);

        $response = $client->request('GET', $url);

        return $response->getBody();
    }



    private function processNinjaTokens(array $data)
    {
        nlog("attempting to process Ninja Tokens");

        if(Ninja::isHosted())
            \Modules\Admin\Jobs\Account\NinjaUser::dispatchNow($data, $this->company);

    }


    /* In V4 we use negative invoices (credits) and add then into the client balance. In V5, these sit off ledger and are applied later.
     This next section will check for credit balances and reduce the client balance so that the V5 balances are correct
    */
    // private function fixClientBalances()
    // {
       
    //     Client::cursor()->each(function ($client) {

    //         $credit_balance = $client->credits->where('is_deleted', false)->sum('balance');

    //         if($credit_balance > 0){
    //             $client->balance += $credit_balance;
    //             $client->save();
    //         }

    //     });

    // }
}
