<?php

namespace App\Http\Controllers;

use App\Events\AppMain;
use App\Events\SystemKickUser;
use App\Events\SystemRefreshUser;
use App\Jobs\UploadToFtp;
use App\Mail\ClientUserAssignment;
use App\Mail\ClientUserInvitation;
use App\Mail\TestEmail;
use App\Mail\UserInvitation;
use App\Models\CrmQbProfile;
use App\Models\QbCreditMemo;
use App\Models\QbPayment;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Mail;
use App\Services\QuickbookService;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Auth;
use App\Services\HelperService;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB;
use App\Services\SPSERPService;
use App\Services\UserService;
use DateTime;
use Exception;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use App\Services\BillingService;
use stdClass;
use App\Models\QbSync;
use DateTimeZone;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\File;
use oasis\names\specification\ubl\schema\xsd\CommonBasicComponents_2\StatusCode;
use QuickBooksOnline\API\Facades\Customer;

class Test extends Controller
{
  private $qbs;
  private $helperService;
  private $spserpService;
  private $billingService;
  private $ato;
  private $userService;
  private $mode;

  public function __construct()
  {
    $this->billingService = new BillingService();
    $this->qbs = new QuickbookService();
    $this->helperService = new HelperService();
    $this->spserpService = new SPSERPService();
    $this->ato = $this->qbs->getStoredAto();
    $this->userService = new UserService();
  }

  /**
   * PENDING
   * CRM-QB profile sync
   */
  public function syncProfiling(Request $request, $entityId, $clientId)
  {
    $amountOfTimeSec = 1800;
    set_time_limit($amountOfTimeSec);
    $data['entity_id'] = $entityId;
    $data['client_id'] = $clientId;
    $entityType = 'Customer';

    $rawData = [];

    // get crm
    $clients = $this->spserpService->getCompaniesBasic(false, false, false, null, true, null, null, null);
    foreach ($clients as $c) {
      // get mapped. single entity but multiple profile maybe
      if ($c['id'] != $clientId) continue;
      $mappedClients = CrmQbProfile::where('entity_id', $entityId)->where('client_id', $clientId)->get();
      foreach ($mappedClients as $mappedC) {
        // prep
        $cdName = $c['name']; // company, display
        $add1 = '';
        $add2 = '';
        if ($c['address1']) {
          $add1 = $c['address1'];
        }
        if ($c['address2']) {
          $add2 = $c['address2']
            . (($c['postcode'] && $c['postcode'] != '') ? $c['postcode'] : '')
            . (($c['city'] && $c['city'] != '') ? (' ' . $c['city']) : '')
            . (($c['state'] && $c['state'] != '') ? (', ' . $c['state'])  : '')
            . (($c['country'] && $c['country'] != '') ? (', ' . $c['country'])  : '');
        } else {
          $add2 = (($c['postcode'] && $c['postcode'] != '') ? $c['postcode'] : '')
            . (($c['city'] && $c['city'] != '') ? (' ' . $c['city']) : '')
            . (($c['state'] && $c['state'] != '') ? (', ' . $c['state'])  : '')
            . (($c['country'] && $c['country'] != '') ? (', ' . $c['country'])  : '');
        }

        // remove trailing comma. trim
        $add1 = trim($add1);
        $add2 = trim($add2);
        if (Str::charAt($add2, 0) == ',') $add2 = substr($add2, 1);
        $add2 = trim($add2);

        // get qb obj. mind curr on name
        $entityAto = $this->qbs->getStoredAto($entityId);
        $qbEntity = $this->qbs->getEntityById($entityAto, $entityType, $mappedC->qb_customer_id);

        $rawData[] = [
          'entity_id' => $entityId, 'client_id' => $clientId, 'qb_client_id' => $mappedC->qb_customer_id,
          'display_name' => $cdName, 'address_line1' => ($add1 != '' ? $add1 : null),
          'address_line2' => ($add2 != '' ? $add2 : null), 'qb_data' => $qbEntity
        ];
      }
    }

    dd($rawData);
  }

  public function getAcmd(Request $request)
  {
    $names = [
      'THP SERVICES SDN BHD (F.K.A THP SINAR SDN BHD)', 'IZ-AN CORPORATION (MALAYSIA) SDN BHD', 'WASIYYAH SHOPPE BERHAD', 'NAZRINA SAMSUDDIN'
    ];
    $kickerTrigger = ['(f'];
    $acmdNames = [];
    foreach ($names as $n) {
      $acmStr = '';
      $nArr = [];
      $tStr = $n;
      foreach ($kickerTrigger as $kt) {
        if (Str::contains(Str::upper($n), Str::upper($kt), true)) {
          $tStr = (explode(Str::upper($kt), Str::upper($n)))[0];
          break;
        }
      }
      Log::debug($tStr);
      $nArr = explode(' ', $tStr);
      Log::debug(count($nArr));
      foreach ($nArr as $na) {
        if (Str::charAt($na, 0) == '(') continue;
        $acmStr .= Str::charAt($na, 0);
      }
      array_push($acmdNames, $acmStr);
    }
    dd([$names, $acmdNames]);
  }

  /**
   * ok
   * for invoice, payment, credit memo
   */
  public function refillExchangeRate(Request $request, $entities, $force)
  {
    Log::debug('refillExchangeRate');
    $amountOfTimeSec = 1800;
    set_time_limit($amountOfTimeSec);

    try {
      $x = '';
      $entityArr = explode(',', $entities);

      if (in_array('Customer', $entityArr)) {
        // Customer
        if ($force) {
          $data = DB::table('qbdata_customers')->select('*')->limit(5000)->get();
        } else {
          $data = DB::table('qbdata_customers')->select('*')->whereNull('qb_billing_address')->orWhereNull('qb_shipping_address')->limit(5000)->get();
        }

        $x1 = 0;
        foreach ($data as $d) {
          $decodedObj = json_decode(base64_decode($d->enc_data_obj));
          $shippingAdd = ($decodedObj->ShipAddr->Line1 ?? '') . ($decodedObj->ShipAddr && $decodedObj->ShipAddr->Line2 ? ' ' . $decodedObj->ShipAddr->Line2 : '')
            . ($decodedObj->ShipAddr && $decodedObj->ShipAddr->Line3 ? ' ' . $decodedObj->ShipAddr->Line3 : '') . ($decodedObj->ShipAddr && $decodedObj->ShipAddr->Line4 ? ' ' . $decodedObj->ShipAddr->Line4 : '')
            . ($decodedObj->ShipAddr && $decodedObj->ShipAddr->Line5 ? ' ' . $decodedObj->ShipAddr->Line5 : '');
          $billingAdd = ($decodedObj->BillAddr->Line1 ?? '') . ($decodedObj->BillAddr && $decodedObj->BillAddr->Line2 ? ' ' . $decodedObj->BillAddr->Line2 : '')
            . ($decodedObj->BillAddr && $decodedObj->BillAddr->Line3 ? ' ' . $decodedObj->BillAddr->Line3 : '') . ($decodedObj->BillAddr && $decodedObj->BillAddr->Line4 ? ' ' . $decodedObj->BillAddr->Line4 : '')
            . ($decodedObj->BillAddr && $decodedObj->BillAddr->Line5 ? ' ' . $decodedObj->BillAddr->Line5 : '');
          DB::table('qbdata_customers')
            ->where('uuid', $d->uuid)
            ->update([
              'qb_customer_parent_id' => $decodedObj->ParentRef ?? null,
              'qb_shipping_address' => $shippingAdd ?? null,
              'qb_billing_address' => $billingAdd ?? null,
            ]);
          $x1++;
        }
        $x .= '-' . $x1;
      }

      if (in_array('CreditMemo', $entityArr)) {
        // CN
        $data = DB::table('qbdata_credit_notes')->select('*')
          ->whereNull('homecurr_balance')
          ->orWhereNull('homecurr_total_amount')
          ->orWhereNull('exchange_rate')
          ->limit(3000)->get();
        foreach ($data as $d) {
          $ocn = DB::table('qbdata_credit_notes')
            ->where('uuid', $d->uuid)->first();
          if ($ocn->homecurr_balance) continue;
          $decodedObj = json_decode(base64_decode($d->enc_data_obj));
          DB::table('qbdata_credit_notes')
            ->where('uuid', $d->uuid)
            ->update([
              'currency' => $decodedObj->CurrencyRef, 'invoice_number' => $decodedObj->InvoiceRef,
              'balance' => $decodedObj->Balance,
              'exchange_rate' => $decodedObj->ExchangeRate ? $decodedObj->ExchangeRate : ($decodedObj->CurrencyRef == 'MYR' ? 1 : null),
              'homecurr_total_amount' => $decodedObj->HomeTotalAmt ? $decodedObj->HomeTotalAmt : ($decodedObj->CurrencyRef == 'MYR' ? $decodedObj->TotalAmt : null),
              'homecurr_balance' => $decodedObj->HomeBalance ? $decodedObj->HomeBalance : ($decodedObj->CurrencyRef == 'MYR' ? $decodedObj->Balance : null)
            ]);
        }
      }

      if (in_array('Invoice', $entityArr)) {
        // Inv
        $data = DB::table('qbdata_invoices')->select('*')
          ->whereNull('exchange_rate')
          ->orWhereNull('homecurr_outstanding_amount')
          ->orWhereNull('homecurr_total_amount')
          ->limit(3000)->get();
        foreach ($data as $d) {
          $oi = DB::table('qbdata_invoices')
            ->where('uuid', $d->uuid)->first();
          if ($oi->homecurr_outstanding_amount) continue;
          $decodedObj = json_decode(base64_decode($d->enc_data_obj));
          DB::table('qbdata_invoices')
            ->where('uuid', $d->uuid)
            ->update([
              'exchange_rate' => $decodedObj->ExchangeRate ? $decodedObj->ExchangeRate : ($decodedObj->CurrencyRef == 'MYR' ? 1 : null),
              'homecurr_total_amount' => $decodedObj->HomeTotalAmt ? $decodedObj->HomeTotalAmt : ($decodedObj->CurrencyRef == 'MYR' ? $decodedObj->TotalAmt : null),
              'homecurr_outstanding_amount' => $decodedObj->HomeBalance ? $decodedObj->HomeBalance : ($decodedObj->CurrencyRef == 'MYR' ? $decodedObj->Balance : null)
            ]);
        }
      }

      if (in_array('Payment', $entityArr)) {
        // Pay
        $data = DB::table('qbdata_payments')->select('*')
          ->whereNull('exchange_rate')
          ->orWhereNull('unapplied_amount')
          ->limit(3000)->get();
        foreach ($data as $d) {
          $decodedObj = json_decode(base64_decode($d->enc_data_obj));
          DB::table('qbdata_payments')
            ->where('uuid', $d->uuid)
            ->update([
              'exchange_rate' => $decodedObj->ExchangeRate ? $decodedObj->ExchangeRate : ($decodedObj->CurrencyRef == 'MYR' ? 1 : null),
              'unapplied_amount' => ($decodedObj->UnappliedAmt && floatval($decodedObj->UnappliedAmt) >= 0) ?? 0,
            ]);
        }
      }


      dd('all good ' . $x);
    } catch (Exception $ex) {
      Log::error($ex);
      dd('error refilling data');
    }
  }

  public function decodeTx(Request $request, $objectType, $objectUuid)
  {
    if ($objectType == 'Invoice') {
      $inv = DB::table('qbdata_invoices')->where('uuid', $objectUuid)->first();
      $decodedObj = json_decode(base64_decode($inv->enc_data_obj));
      dd($decodedObj);
    }
    if ($objectType == 'Payment') {
      $inv = DB::table('qbdata_payments')->where('uuid', $objectUuid)->first();
      $decodedObj = json_decode(base64_decode($inv->enc_data_obj));
      dd($decodedObj);
    }
    if ($objectType == 'CreditMemo') {
      $inv = DB::table('qbdata_credit_notes')->where('uuid', $objectUuid)->first();
      $decodedObj = json_decode(base64_decode($inv->enc_data_obj));
      dd($decodedObj);
    }
    if ($objectType == 'Customer') {
      $inv = DB::table('qbdata_customers')->where('uuid', $objectUuid)->first();
      $decodedObj = json_decode(base64_decode($inv->enc_data_obj));
      dd($decodedObj);
    }
  }

  /**
   * customerbalance test
   */
  public function getCustomerBalance(Request $request, $entityId, $customerId)
  {
    Log::debug("$entityId $customerId");
    // $lastSyncDtIso = date_format(date_create("2024-02-27 15:00:03"), 'c');
    // $oriDtStr = "2024-02-28 00:00:00";
    // $startDate = date_format(date_create("2023-01-01 00:00:01"), 'c');
    // $endDate = date_format(date_create("2023-12-31 11:59:59"), 'c');
    $startDate = '2023-01-01';
    $endDate = '2023-12-31';
    try {
      $entityAto = $this->qbs->getStoredAto($entityId);
      // $res = $this->qbs->getCustomerBalance($entityAto, $customerId, $endDate);
      $res = $this->qbs->getCustomerBalance($entityAto, $customerId, $startDate, $endDate);
      if ($res) {
        dd($res);
      } else {
        echo "All good";
      }
    } catch (Exception $ex) {
      echo "Error in syncing changes";
    }
  }

  /**
   * PENDING
   * backend only
   * - read all customers in qbdata_customers
   * - decode object and user active property to update
   */
  public function recompleteQbCustomers()
  {
  }

  /**
   * UNUSED - practive use memo instead lol
   * - check for invoiceref
   */
  public function recompleteQbCreditMemo()
  {
    $amountOfTimeSec = 1800;
    set_time_limit($amountOfTimeSec);
    $creditMemos = QbCreditMemo::get();
    $data['count'] = count($creditMemos);
    // $data['first'] = $creditMemos[0];
    // $data['first_object'] = json_decode(base64_decode($creditMemos[0]->enc_data_obj));
    foreach ($creditMemos as $c) {
      $dataObj = json_decode(base64_decode($c->enc_data_obj));
      if ($dataObj->Memo != null) {
        $data['first_object_wth_inv'] = $dataObj;
        break;
      }
    }
    dd($data);
  }

  /**
   * OK
   * - fill unapplied_amount form object
   * - fill memo
   */
  public function recompleteQbPayment($uuid = null)
  {
    $amountOfTimeSec = 1800;
    set_time_limit($amountOfTimeSec);
    $payments = QbPayment::get();
    $data['count'] = count($payments);
    foreach ($payments as $p) {
      $dataObj = json_decode(base64_decode($p->enc_data_obj));
      $saving = false;
      if ($dataObj->PrivateNote != null && $dataObj->PrivateNote != 0 && $dataObj->PrivateNote != '') {
        $p->memo = $dataObj->PrivateNote;
        $saving = true;
      }
      if ($dataObj->PaymentRefNum != null && $dataObj->PaymentRefNum != 0 && $dataObj->PaymentRefNum != '') {
        $p->qb_payment_ref_number = $dataObj->PaymentRefNum;
        $saving = true;
      }
      if ($dataObj->TxnDate != null && $dataObj->TxnDate != '0000-00-00' && $dataObj->TxnDate != '') {
        $p->payment_date = $dataObj->TxnDate;
        $saving = true;
      }
      if ($saving) $p->save();

      // $unappliedAmount = $dataObj->UnappliedAmt;
      // $p->unapplied_amount = $unappliedAmount;
      // $p->save();
    }
    $data['status'] = 'ok';
    dd($data);
  }

  // api - upload
  public function uploadOne(Request $request)
  {
    $validator = Validator::make($request->all(), [
      'document' => 'required|file',
    ]);

    if ($validator->fails()) {
      return response()->json([
        'success' => false,
        'message' => 'Invalid data provided'
      ]);
    }

    $rF = "uer/" . Str::uuid();
    Storage::makeDirectory($rF);
    $uf = $request->file('document');
    $uf->storeAs($rF, Str::uuid() . "." . $uf->extension());

    return response()->json([
      'success' => true
    ], 200);
  }

  // ok
  public function syncChangesSinceLast(Request $request)
  {
    echo "syncChangesSinceLast<br>";
    $lastSyncDtIso = date_format(date_create("2024-02-27 15:00:03"), 'c');
    $oriDtStr = "2024-02-28 00:00:00";
    try {
      foreach ($this->ato as $anAto) {
        if (!$this->qbs->getChanges($anAto, $oriDtStr)) throw new Exception('CDC error');
      }
      echo "All good";
    } catch (Exception $ex) {
      echo "Error in syncing changes";
    }
  }

  public function getCompanyByEntityId(Request $request, $entityId)
  {
    $k = array_search($entityId, array_column((array) $this->ato, 'entity_id'));
    if (gettype($k) == 'boolean') echo 'invalid data';

    $data = $this->qbs->getCompanyCurrencies($this->ato[$k]);
    dd($data);
  }

  /**
   * First, create parent customer objects: Set Job to false (default) and do not define ParentRef.
   * Then, create sub-customer and job objects: Set Job to true and set ParentRef to reference parent customer object.
   */
  public function processWriteOp(Request $request, $op, $entityId)
  {
    echo "processWriteOp<br>";
    if (!in_array($op, ['Create', 'Update']) || !in_array($entityId, [7, 13, 5])) {
      echo "invalid op";
      return;
    }
    echo "Ready<br>";

    // sample
    $customerObj = Customer::create([
      "BillAddr" => [
        "Line1" => "123 Main Street",
        "City" => "Mountain View",
        "Country" => "USA",
        "CountrySubDivisionCode" => "CA",
        "PostalCode" => "94042"
      ],
      "Notes" => "Here are other details.",
      "FullyQualifiedName" => "VORTEX GROUP:VORTEX PLATINUM SDN BHD",
      "CompanyName" => "VORTEX PLATINUM SDN BHD",
      "DisplayName" => "VORTEX PLATINUM SDN BHD",
      "PrimaryPhone" => [
        "FreeFormNumber" => "(555) 555-5555"
      ],
      "PrimaryEmailAddr" => [
        "Address" => "jdrew@myemail.com"
      ],
    ]);
    $op = 'create';

    try {
      // update only specific entity i.e. SITC
      foreach ($this->ato as $anAto) {
        if ($anAto->entity_id != $entityId) continue;

        // $opData = [];
        // $opData[] = [
        //   'data' => $customerObj, 'identifier' => Str::uuid()->toString(), 'op' => 'create'
        // ];
        if (!$this->qbs->doBatchWrite($anAto, $customerObj, Str::uuid()->toString(), $op)) {
          echo "Failed";
          break;
        }
        echo "All good";
      }
    } catch (Exception $ex) {
      echo "Error in processing qb write";
    }
  }

  public function getSingularQbData(Request $request, $entityId, $objectStr)
  {
    echo "getSingularQbData $objectStr for $entityId <br>";
    $amountOfTimeSec = 1800;
    set_time_limit($amountOfTimeSec);

    // $qs = config('quickbook.query_size');
    $qs = 100;

    // get prep sync only
    $workingSync = DB::table('qb_syncs')->where('status', 1)->whereNull('deleted_at')->whereNotNull('prepared_object')->whereNotNull('prepared_at')->orderBy('id', 'desc')->first();
    if (!$workingSync) return 0;
    $syncTime = now();

    // use prep data to construt batch op
    $prepData = (array) json_decode(base64_decode($workingSync->prepared_object));

    try {
      DB::beginTransaction();
      foreach ($this->ato as $anAto) {
        if ($anAto->entity_id != $entityId) continue;

        Log::debug("$objectStr be with id: " . $anAto->entity_id);

        // batch per business entity
        $batchRequestStrArr = [];

        $s = $prepData[$entityId]->$objectStr;
        Log::debug("size $s");
        $iteration = (floor(intval($s) / $qs)) + 1;
        $start = 1;
        $seq = 1;
        do {
          if ($workingSync->cut_off_time) {
            $wscot = date_format(date_create($workingSync->cut_off_time), 'c');
            // Log::debug('sync: ' . $wscot);
            $tStr = "select * from $objectStr where MetaData.CreateTime > '$wscot' startPosition $start maxResults $qs";
          } else {
            $tStr = "select * from $objectStr startPosition $start maxResults $qs";
          }
          $batchRequestStrArr[$objectStr . '-' . $seq] = $tStr;
          $start += $qs;
          $seq++;
        } while ($start < (($iteration * $qs) + 1));
        Log::debug('bqsa: ' . count($batchRequestStrArr));
        Log::debug(print_r($batchRequestStrArr, true));

        // chunking
        $chunkedBatchRequestArr = array_chunk($batchRequestStrArr, 10, true); // working for all but invoice - timout
        // $chunkedBatchRequestArr = array_chunk($batchRequestStrArr, 30, true); // working for all but invoice - timout
        Log::debug("chunked count: " . count($chunkedBatchRequestArr));
        foreach ($chunkedBatchRequestArr as $cbra) {
          Log::debug('chunk size: ' . count($cbra));
          // Log::debug(print_r($cbra, true));
          // Log::debug("uuid: " . $workingSync->uuid);
          // if(!$this->qbs->doBatchOp2($anAto, $cbra, $workingSync->uuid)) break;
          if (!$this->qbs->doSingle($anAto, $cbra, $workingSync->uuid)) break;
        }
      }
      DB::commit();

      dd('completed');
      // DB::table('qb_syncs')->where([
      //     ['status','=',1], ['uuid','=',$workingSync->uuid]
      // ])->whereNull('deleted_at')->update([
      //     'status' => 2, 'synced_at' => $syncTime
      // ]);
    } catch (Exception $ex) {
      DB::rollBack();
      DB::table('qb_syncs')->where([
        ['status', '=', 1], ['uuid', '=', $workingSync->uuid]
      ])->whereNull('deleted_at')->update([
        'status' => 0, 'error_object' => 'error syncing: ' . $ex
      ]);
    }
  }

  public function getBatchQbData(Request $request)
  {
    echo 'getBatchQbData<br>';
    $amountOfTimeSec = 1800;
    set_time_limit($amountOfTimeSec);

    $qs = config('quickbook.query_size');

    $qbComponents = DB::table('qb_objects')->where('status', 1)->get()->pluck('name')->toArray();
    echo count($qbComponents) . '<br>';
    // get prep sync only
    $workingSync = DB::table('qb_syncs')->where('status', 1)->whereNull('deleted_at')->whereNotNull('prepared_object')->whereNotNull('prepared_at')->orderBy('id', 'desc')->first();
    if (!$workingSync) return 0;
    $syncTime = now();

    // use prep data to construt batch op
    $prepData = (array) json_decode(base64_decode($workingSync->prepared_object));

    try {
      DB::beginTransaction();
      $y = 1;
      foreach ($this->ato as $anAto) {
        Log::debug("$y - be id: " . $anAto->entity_id);
        // Log::debug(print_r($anAto->entity_id, true));
        // batch per business entity
        $batchRequestStrArr = [];

        foreach ($qbComponents as $qbc) {
          $s = $prepData[$anAto->entity_id]->$qbc;
          Log::debug("size $s");
          $iteration = (floor(intval($s) / $qs)) + 1;
          // Log::debug("iteration $iteration");
          $start = 1;
          $seq = 1;
          do {
            if ($workingSync->cut_off_time) {
              $wscot = date_format(date_create($workingSync->cut_off_time), 'c');
              // Log::debug('sync: ' . $wscot);
              $tStr = "select * from $qbc where MetaData.CreateTime > '$wscot' startPosition $start maxResults $qs";
            } else {
              $tStr = "select * from $qbc startPosition $start maxResults $qs";
            }
            // array_push($batchRequestStrArr, $tStr);
            $batchRequestStrArr[$qbc . '-' . $seq] = $tStr;
            $start += $qs;
            $seq++;
          } while ($start < (($iteration * $qs) + 1));

          // execute
          // Log::debug(print_r($batchRequestStrArr, true));
        }
        Log::debug('bqsa: ' . count($batchRequestStrArr));
        Log::debug(print_r($batchRequestStrArr, true));

        // chunking
        $chunkedBatchRequestArr = array_chunk($batchRequestStrArr, 3, true); // working for all but invoice - timout
        // $chunkedBatchRequestArr = array_chunk($batchRequestStrArr, 30, true); // working for all but invoice - timout
        Log::debug("chunked count: " . count($chunkedBatchRequestArr));
        foreach ($chunkedBatchRequestArr as $cbra) {
          Log::debug('chunk size: ' . count($cbra));
          // Log::debug(print_r($cbra, true));
          // Log::debug("uuid: " . $workingSync->uuid);
          // if(!$this->qbs->doBatchOp2($anAto, $cbra, $workingSync->uuid)) break;
          if (!$this->qbs->doSingle($anAto, $cbra, $workingSync->uuid)) break;
        }

        $y++;

        // if one op failed, cancel the whole sync
        // if(!$this->qbs->doBatchOp($anAto, $batchRequestStrArr, $workingSync->uuid)) break;
      }
      DB::commit();

      dd('all good');
      // DB::table('qb_syncs')->where([
      //     ['status','=',1], ['uuid','=',$workingSync->uuid]
      // ])->whereNull('deleted_at')->update([
      //     'status' => 2, 'synced_at' => $syncTime
      // ]);
    } catch (Exception $ex) {
      DB::rollBack();
      DB::table('qb_syncs')->where([
        ['status', '=', 1], ['uuid', '=', $workingSync->uuid]
      ])->whereNull('deleted_at')->update([
        'status' => 0, 'error_object' => 'error syncing: ' . $ex
      ]);
    }
  }

  // Billing
  // public function billingAll()
  // {
  //   $this->mode = session()->get('active_mode');
  //   if ($this->mode == 3) {
  //     $data = $this->billingService->getConsumerInvoices(session()->get('active_company'));
  //   } else {
  //     $data = null;
  //   }
  //   dd($data);
  // }

  /**
   * use invoice data
   * 1. get all invoices from spserp. client_id !=0
   * 2. for each
   * - get qb company id and name based on invoice number and entity id
   * - check that combination doesnt exists in cqm
   * - add combination
   */
  public function populateCrmByInvoices()
  {
    $amountOfTimeSec = 1800;
    set_time_limit($amountOfTimeSec);
    $ei = DB::table('allowed_business_entities as abe')->selectRaw('GROUP_CONCAT(abe.entity_id) as entity_ids')->where('abe.status', 1)->first();
    $invoices = $this->spserpService->getAllInvoices(false, false, null, null, true, $ei->entity_ids);
    // $data['invoices_count'] = count($invoices);
    // $data['invoices'] = $invoices[10];
    $added = [];
    foreach ($invoices as $i) {
      if (!$i['inv_form_no'] || $i['inv_form_no'] == '' || !$i['client_id'] || $i['client_id'] == '' || $i['client_id'] == 0) continue;
      $qbdata = DB::table('qbdata_invoices as qi')->select('qi.*')
        ->join('qb_syncs as qs', function ($join) {
          $join->on('qs.uuid', '=', 'qi.sync_uuid')->where('qs.status', 2);
        })
        ->where([
          ['qi.status', '=', 1], ['qi.business_entity_id', '=', $i['business_entity_id']], ['qi.invoice_number', '=', $i['inv_form_no']]
        ])
        ->first();

      if (!$qbdata) continue;

      if (DB::table('client_quickbook_mappings')->where([
        ['entity_id', '=', $qbdata->business_entity_id], ['qb_customer_id', '=', $qbdata->qb_customer_id], ['client_id', '=', $i['client_id']], ['status', '=', 1]
      ])->exists()) continue;

      $qbcdata = DB::table('qbdata_customers as qc')->select('qc.*')
        ->join('qb_syncs as qs', function ($join) {
          $join->on('qs.uuid', '=', 'qc.sync_uuid')->where('qs.status', 2);
        })
        ->where([
          ['qc.business_entity_id', '=', $qbdata->business_entity_id], ['qc.qb_customer_id', '=', $qbdata->qb_customer_id], ['qc.status', '=', 1]
        ])
        ->first();
      if (!$qbcdata) continue;

      // add
      DB::table('client_quickbook_mappings')->insert([
        'client_id' => $i['client_id'],
        'entity_id' => $qbdata->business_entity_id,
        'qb_customer_id' => $qbdata->qb_customer_id,
        'name' => $qbcdata->qb_company_name ?? $qbcdata->qb_display_name,
        'display_name' => $qbcdata->qb_display_name,
        'fqn_name' => $qbcdata->qb_fqn,
        'created_at' => now()
      ]);

      array_push($added, $i['client_id']);
    }
    $data['added_count'] = count($added);
    dd($data);
  }

  // QB

  public function qbBatchSizes()
  {
    $prepData = [];
    $isoDatetime = date_format(date_create('2024-01-01 00:00:01'), 'c');
    $prepData['cut_off_time'] = $isoDatetime;
    foreach ($this->ato as $anAto) {
      $ui = Str::uuid();
      $prepData[$anAto->entity_id] = $this->qbs->prepBatchOp2(Crypt::decrypt($anAto->token_object), $ui, $isoDatetime);
    }
    dd($prepData);
  }

  public function qbChangedEntities()
  {
    $isoDatetime = '2024-01-01T00:00:01';
    // $isoDatetime = date_format(date_create('2024-01-19 00:00:01'),'c');

    foreach ($this->ato as $anAto) {
      $this->qbs->getChangedEntities(Crypt::decrypt($anAto->token_object), $isoDatetime);
    }
  }

  public function getUserInfo(Request $request)
  {
    try {
      $rud = $this->spserpService->getUser($request->input('email'), 'email');
      $providers = $this->userService->getProviders($request->input('email'));
      $modes = DB::table('modes')->select('id', 'name', 'rank_order', 'access_type')->where('status', 1)->orderBy('rank_order', 'desc')->get();
      $userRoles = [];
      Log::debug($modes);
      foreach ($modes as $mk => $mv) {
        if ($mv->access_type === 'internal') {
          $ir = $this->userService->getInternalRoles2($rud, $mv->id, $mv->id == 2);
          if (!$ir || count($ir) == 0) continue;
          $userRoles = array_merge($userRoles, $ir);
        }
        // if ($mv->access_type === 'external') {
        //   $er = $this->userService->getExternalRoles($rud, $mv->id);
        //   if (!$er) continue;
        //   $userRoles = array_merge($userRoles, $er);
        // }
      }
      return response()->json([
        'success' => true,
        'message' => 'ok',
        'data' => [
          'info' => $rud,
          'providers' => $providers,
          'roles' => $userRoles
        ]
      ]);
    } catch (Exception $ex) {
      Log::error($ex);
      return response()->json([
        'success' => false,
        'message' => 'error: ' . $ex,
        'data' => null
      ]);
    }
  }

  public function uploadViaSftp(Request $request)
  {
    try {
      // $data['path'] = $request->file('attachment')->storeAs('cp-dev-sftp', 'some-file', 'sftp');

      $data = null;
      $file = request()->file('attachment');
      Log::debug('queueing for upload');
      UploadToFtp::dispatch($file);
      // $disk = Storage::disk('sftp');
      // $fileOK = $disk->put('file/' . $file->getClientOriginalName(), file_get_contents($file->getRealPath()));
      // $disk->putFileAs('custom_filename', $file, 'path/to/folder');
      return response()->json([
        'success' => true,
        'message' => 'ok',
        'data' => $data
      ]);
    } catch (Exception $ex) {
      Log::error($ex);
      return response()->json([
        'success' => false,
        'message' => 'error: ' . $ex,
        'data' => null
      ]);
    }
  }

  public function sendEmail($email)
  {
    Mail::to($email)->send(new TestEmail("This is a test email sent on " . date('Y-m-d H:i:s')));
    echo "Email sent";
  }

  /**
   * get all companies
   * filter out incomplete - no type, no status
   * check if qb entry exists
   */
  public function populateCrmData(Request $request)
  {
    $companies = $this->spserpService->getCompanies(false, false, false, null, false, null, 1400, 200, 'name', false, null, null);
    $data['companies_all'] = count($companies);
    // $data['sample'] = $companies[0];
    $addedC = [];
    $decdAto = Crypt::decrypt($request->session()->get('qb_ato'));
    foreach ($companies as $c) {
      // check if exists
      $tC = $this->qbs->getCustomerByName($decdAto, $c['name']);
      if ($tC && count($tC) > 0) continue;
      if ($this->qbs->createCustomer($decdAto, $c)) array_push($addedC, $c);
    }
    $data['added_companies'] = count($addedC);
    dd($data);
  }

  // public function getCustomers(Request $request){
  //     $companies = $this->qbs->getCustomers(Crypt::decrypt($request->session()->get('qb_ato')));
  //     dd($companies);
  // }

  public function getActiveUser(Request $request)
  {
    $dyno = new stdClass();
    $qbo = DB::table('qb_objects')->where('status', 1)->get()->pluck('name')->toArray();
    foreach ($qbo as $prop) {
      $dyno->$prop = 12;
    }
    $data['dyno'] = $dyno;
    $data['message'] = 'active user';
    $data['user'] = Auth::user();
    $data['active_session'] = Auth::user()->active_session;
    $data['active_picture'] = Auth::user()->active_picture;
    $data['active_roles'] = Auth::user()->active_roles;
    $data['active_mode'] = $request->session()->get('active_mode');
    $data['active_entity'] = session()->has('active_entity') ? $request->session()->get('active_entity') : null;
    $data['active_company'] = session()->has('active_company') ? $request->session()->get('active_company') : null;
    // $data['active_roles'] = session()->get('active_roles');
    // print_r($data);
    $data['active_roles_dec'] = Crypt::decrypt($data['active_roles']);
    dd($data);
    // dd($this->helperService->getMenu($request->session()->get('active_mode')));
  }



  public function initQb()
  {
    Log::debug('initQb');
    $url = $this->qbs->getAuthCodeURL();
    Log::debug($url);
    return redirect()->away($url);
    // header("Location:" . $qbs->getAuthCodeURL());
  }

  public function qbInfo()
  {
    // echo 'lol';
    // $i = DB::table('apis')->where([
    //   ['code', '=', 'qb'], ['status', '=', 1]
    // ])->first();
    // $data['app_info'] = Crypt::decrypt($i->api_info);
    // $data['token_object'] = Crypt::decrypt($i->token_object);
    $data['sync_has_pending'] = QbSync::where('status', 1)->whereNull('deleted_at')->exists();
    dd($data);
  }

  public function initQbCallback(Request $request)
  {
    Log::debug('initQbCallback');
    Log::debug($request->all());
    $ato = $this->qbs->getAccessTokenObject($request->post('code'), $request->post('realmId'));
    $request->session()->put('ato', $ato);
    return redirect('/test/qb/company');
  }

  public function getCompany(Request $request, $entityId)
  {
    Log::debug('getCompany');
    if ($request->session()->has('qb_ato')) {
      $ato = $request->session()->get('qb_ato');
      if ($ci = $this->qbs->getCompanyInfo(Crypt::decrypt($ato))) {
        $data['company_info'] = $ci;
      } else {
        $data['message'] = 'qb error';
      }
      // $data['company_preferences'] = $this->qbs->getCompanyPreferences($ato);
    } else {
      $data['message'] = 'failed to get company info';
      $data['company_info'] = null;
    }
    dd($data);
  }

  public function getPayments(Request $request, $entityId = null)
  {
    print_r('getPayments<br>');
    print_r($entityId ? 'entity id: ' . $entityId : 'no entity id given');
    print_r('<br>');

    if ($entityId) {
      $beAto = $this->qbs->getStoredAto($entityId);
      if (!$beAto) die('invalid entity id');
      $data['rows'] = $this->qbs->getPayments(Crypt::decrypt($beAto->token_object), null, 1, 1000);
    } else {
      $paymentArr = [];
      foreach ($this->ato as $anAto) {
        $paymentEl = $data['rows'] = $this->qbs->getItems(Crypt::decrypt($anAto->token_object), null, 1, 1000);
        for ($x = 0; $x < count($paymentEl); $x++) {
          $paymentEl[$x]->EntityId = $anAto->entity_id;
        }
        $paymentArr = array_merge($paymentArr, $paymentEl);
      }
      $data['rows'] = $paymentArr;
    }

    print_r($data);
    die();
  }

  public function getEntityPaymentById(Request $request, $entityId, $paymentId)
  {
    print_r('getEntityPaymentById<br>');
    print_r('entity id: ' . $entityId);
    print_r('<br>');
    print_r('payment id: ' . $paymentId);
    print_r('<br>');

    $beAto = $this->qbs->getStoredAto($entityId);
    if (!$beAto) die('invalid entity id');
    $payment = $this->qbs->getPayments(Crypt::decrypt($beAto->token_object), $paymentId);
    if (!$payment) die('invalid payment id');

    print_r($payment);
    die();
  }

  public function getPaymentMethods(Request $request, $entityId = null)
  {
    print_r('getPaymentMethods<br>');
    print_r($entityId ? 'entity id: ' . $entityId : 'no entity id given');
    print_r('<br>');

    if ($entityId) {
      $beAto = $this->qbs->getStoredAto($entityId);
      if (!$beAto) die('invalid entity id');
      $data['rows'] = $this->qbs->getPaymentMethods(Crypt::decrypt($beAto->token_object), null, 1, 1000);
    } else {
      $pmArr = [];
      foreach ($this->ato as $anAto) {
        $pmEl = $data['rows'] = $this->qbs->getPaymentMethods(Crypt::decrypt($anAto->token_object), null, 1, 1000);
        for ($x = 0; $x < count($pmEl); $x++) {
          $pmEl[$x]->EntityId = $anAto->entity_id;
        }
        $pmArr = array_merge($pmArr, $pmEl);
      }
      $data['rows'] = $pmArr;
    }

    print_r($data);
    die();
  }

  public function getItems(Request $request, $entityId = null)
  {
    print_r('getItems<br>');
    print_r($entityId ? 'entity id: ' . $entityId : 'no entity id given');
    print_r('<br>');

    if ($entityId) {
      $beAto = $this->qbs->getStoredAto($entityId);
      if (!$beAto) die('invalid entity id');
      $data['rows'] = $this->qbs->getItems(Crypt::decrypt($beAto->token_object), null, 1, 1000);
    } else {
      $itemsArr = [];
      foreach ($this->ato as $anAto) {
        $itemEl = $data['rows'] = $this->qbs->getItems(Crypt::decrypt($anAto->token_object), null, 1, 1000);
        for ($x = 0; $x < count($itemEl); $x++) {
          $itemEl[$x]->EntityId = $anAto->entity_id;
        }
        $itemsArr = array_merge($itemsArr, $itemEl);
      }
      $data['rows'] = $itemsArr;
    }

    print_r($data);
    die();
  }

  public function getEntityItemById(Request $request, $entityId, $itemId)
  {
    print_r('getEntityItemById<br>');
    print_r('entity id: ' . $entityId);
    print_r('<br>');
    print_r('item id: ' . $itemId);
    print_r('<br>');

    $beAto = $this->qbs->getStoredAto($entityId);
    if (!$beAto) die('invalid entity id');
    $item = $this->qbs->getItems(Crypt::decrypt($beAto->token_object), $itemId);
    if (!$item) die('invalid item id');

    print_r($item);
    die();
  }

  // all or by entities
  public function getCustomers(Request $request, $id = null)
  {
    print_r('getCustomers<br>');
    print_r($id ? 'entity id: ' . $id : 'no entity id given');
    print_r('<br>');

    if ($id) {
      $beAto = $this->qbs->getStoredAto($id);
      if (!$beAto) die('invalid entity id');
      $data['rows'] = $this->qbs->getCustomers2(
        Crypt::decrypt($beAto->token_object),
        null,
        1,
        1000
      );
    } else {
      $cArr = [];
      foreach ($this->ato as $anAto) {
        $cEl = $this->qbs->getCustomers2(
          Crypt::decrypt($anAto->token_object),
          null,
          1,
          1000
        );
        for ($x = 0; $x < count($cEl); $x++) {
          $cEl[$x]->EntityId = $anAto->entity_id;
        }
        $cArr = array_merge($cArr, $cEl);
      }
      $data['rows'] = $cArr;
    }

    print_r($data);
    die();
  }

  public function getEntityCustomerById(Request $request, $entityId, $customerId)
  {
    print_r('getEntityCustomerById<br>');
    print_r('entity id: ' . $entityId);
    print_r('<br>');
    print_r('customer id: ' . $customerId);
    print_r('<br>');

    $beAto = $this->qbs->getStoredAto($entityId);
    if (!$beAto) die('invalid entity id');
    $c = $this->qbs->getCustomers2(
      Crypt::decrypt($beAto->token_object),
      $customerId
    );
    if (!$c) die('invalid customer id');

    print_r($c);
    die();
  }

  public function getInvoices(Request $request, $id = null)
  {
    print_r('getInvoices<br>');
    print_r($id ? 'entity id: ' . $id : 'no entity id given');
    print_r('<br>');

    if ($id) {
      $beAto = $this->qbs->getStoredAto($id);
      if (!$beAto) die('invalid entity id');
      $data['rows'] = $this->qbs->getInvoices(
        Crypt::decrypt($beAto->token_object),
        null,
        1,
        1000
      );
    } else {
      $invArr = [];
      foreach ($this->ato as $anAto) {
        $invEl = $this->qbs->getInvoices(
          Crypt::decrypt($anAto->token_object),
          null,
          1,
          1000
        );
        for ($x = 0; $x < count($invEl); $x++) {
          $invEl[$x]->EntityId = $anAto->entity_id;
        }
        $invArr = array_merge($invArr, $invEl);
      }
      $data['rows'] = $invArr;
    }

    print_r($data);
    die();
  }

  public function getEntityInvoiceByNumber(Request $request, $entityId, $invoiceNumber)
  {
    print_r('getInvoice<br>');
    print_r('entity id: ' . $entityId);
    print_r('<br>');
    print_r('inv number: ' . $invoiceNumber);
    print_r('<br>');

    $beAto = $this->qbs->getStoredAto($entityId);
    if (!$beAto) die('invalid entity id');
    $inv = $this->qbs->getInvoices(
      Crypt::decrypt($beAto->token_object),
      $invoiceNumber
    );
    if (!$inv) die('invalid invoice number');

    print_r($inv);
    die();
  }

  public function doBatch(Request $request, $entityId)
  {
    print_r('dBatch<br>');
    print_r('entity id: ' . $entityId);
    print_r('<br>');
    $beAto = $this->qbs->getStoredAto($entityId);
    if (!$beAto) die('invalid entity id');
    $ui = Str::uuid();
    $inv = $this->qbs->getBatchData(Crypt::decrypt($beAto->token_object), 'all', $ui, 1, 100);
    print_r($inv);
    die();
  }

  public function getMenu(Request $request)
  {
    dd($this->helperService->getMenu(
      session()->get('active_mode'),
      session()->get('active_company')['id'],
      session()->get('active_roles')
    ));
  }

  public function getDataCount(Request $request, $dataType, $entityId = null)
  {
    print_r("getDataCount for $dataType<br>");
    print_r($entityId ? 'entity id: ' . $entityId : 'no entity id given');
    print_r('<br>');

    switch ($dataType) {
      case 'Invoice':
        if ($entityId) {
          $beAto = $this->qbs->getStoredAto($entityId);
          if (!$beAto) die('invalid entity id');
          $data['rows'][$entityId] = $this->qbs->getInvoices(Crypt::decrypt($beAto->token_object), null, 1, 1000, true);
        } else {
          $invArr = [];
          foreach ($this->ato as $anAto) {
            $invEl = $this->qbs->getInvoices(Crypt::decrypt($anAto->token_object), null, 1, 1000, true);
            $invArr[$anAto->entity_id] = $invEl;
          }
          $data['rows'] = $invArr;
        }
        break;
      case 'Item':
        if ($entityId) {
          $beAto = $this->qbs->getStoredAto($entityId);
          if (!$beAto) die('invalid entity id');
          $data['rows'][$entityId] = $this->qbs->getItems(Crypt::decrypt($beAto->token_object), null, 1, 1000, true);
        } else {
          $invArr = [];
          foreach ($this->ato as $anAto) {
            $invEl = $this->qbs->getItems(Crypt::decrypt($anAto->token_object), null, 1, 1000, true);
            $invArr[$anAto->entity_id] = $invEl;
          }
          $data['rows'] = $invArr;
        }
        break;
      case 'Payment':
        if ($entityId) {
          $beAto = $this->qbs->getStoredAto($entityId);
          if (!$beAto) die('invalid entity id');
          $data['rows'][$entityId] = $this->qbs->getPayments(Crypt::decrypt($beAto->token_object), null, 1, 1000, true);
        } else {
          $invArr = [];
          foreach ($this->ato as $anAto) {
            $invEl = $this->qbs->getPayments(Crypt::decrypt($anAto->token_object), null, 1, 1000, true);
            $invArr[$anAto->entity_id] = $invEl;
          }
          $data['rows'] = $invArr;
        }
        break;
      case 'PaymentMethod':
        if ($entityId) {
          $beAto = $this->qbs->getStoredAto($entityId);
          if (!$beAto) die('invalid entity id');
          $data['rows'][$entityId] = $this->qbs->getPaymentMethods(Crypt::decrypt($beAto->token_object), null, 1, 1000, true);
        } else {
          $invArr = [];
          foreach ($this->ato as $anAto) {
            $invEl = $this->qbs->getPaymentMethods(Crypt::decrypt($anAto->token_object), null, 1, 1000, true);
            $invArr[$anAto->entity_id] = $invEl;
          }
          $data['rows'] = $invArr;
        }
        break;
      case 'Customer':
        if ($entityId) {
          $beAto = $this->qbs->getStoredAto($entityId);
          if (!$beAto) die('invalid entity id');
          $data['rows'][$entityId] = $this->qbs->getCustomers2(Crypt::decrypt($beAto->token_object), null, 1, 1000, true);
        } else {
          $invArr = [];
          foreach ($this->ato as $anAto) {
            $invEl = $this->qbs->getCustomers2(Crypt::decrypt($anAto->token_object), null, 1, 1000, true);
            $invArr[$anAto->entity_id] = $invEl;
          }
          $data['rows'] = $invArr;
        }
        break;
    }



    print_r($data);
    die();
  }

  public function getChanges(Request $request)
  {
    print_r('getChanges<br>');
    print_r('<br>');

    $changes = [];
    foreach ($this->ato as $anAto) {
      $changes[$anAto->entity_id] = $this->qbs->getLastChanges(Crypt::decrypt($anAto->token_object));
    }
    $data['rows'] = $changes;
    print_r($data);
    die();
  }

  // invite a user to a role fo a company by email
  public function inviteMember(Request $request)
  {
    $validator = Validator::make($request->all(), [
      'company_id' => 'required|integer',
      'email' => 'required|email',
      'role_id' => 'required|exists:roles,id'
    ]);

    if ($validator->fails()) {
      return response()->json([
        'success' => false,
        'message' => 'Invalid data provided'
      ]);
    }
    try {
      $inviterId = Auth::user() ? Auth::user()->id : '6781fbaa-2061-487e-a4a3-d3e15e7c632d';

      Log::debug('A');

      // get setting for user and current user count
      $currUserStatus = DB::table('settings_company_users as scu')
        ->select(
          'scu.role_id',
          'scu.user_limit',
          DB::raw('(select count(cu.id) from client_users cu where cu.status = 1 and cu.role_id = scu.role_id and cu.client_id = ' .
            $request->input('company_id') . ') as user_count'),
          DB::raw('(select count(cui.id) from client_user_invites cui where cui.status = 1 and cui.role_id = scu.role_id and cui.client_id = ' .
            $request->input('company_id') . ') as pending_invite_count')
        )
        ->where([
          ['scu.status', '=', 1], ['scu.role_id', '=', $request->input('role_id')]
        ])
        ->first();
      if (($currUserStatus->user_count >= $currUserStatus->user_limit) || ($currUserStatus->user_count + $currUserStatus->pending_invite_count >= $currUserStatus->user_limit)) {
        return response()->json([
          'success' => false,
          'message' => 'No user can no longer be added for this role. Remove any pending invite if any.'
        ]);
      }

      Log::debug('B');

      // get inviter and company name
      $company = $this->spserpService->getCompany($request->input('company_id'));
      Log::debug($company);
      $inviter = DB::table('users as u')
        ->leftJoin('user_names as un', function ($join) {
          $join->on('un.user_id', '=', 'u.id')->where('un.status', '=', 1);
        })
        ->leftJoin('title_types as tt', function ($join) {
          $join->on('tt.id', '=', 'un.title_id')->where('tt.status', '=', 1);
        })
        ->select('u.id', 'u.email', 'un.first_name', 'un.last_name', 'tt.name')
        ->where([
          ['u.id', '=', $inviterId]
        ])->whereNull('u.deleted_at')->first();
      $role = DB::table('roles')->where([['status', '=', 1], ['id', '=', $request->input('role_id')]])->first();

      Log::debug('C');

      // check if exisiting user
      $user = DB::table('users as u')
        ->leftJoin('user_names as un', function ($join) {
          $join->on('un.user_id', '=', 'u.id')->where('un.status', '=', 1);
        })
        ->leftJoin('title_types as tt', function ($join) {
          $join->on('tt.id', '=', 'un.title_id')->where('tt.status', '=', 1);
        })
        ->select('u.id', 'u.email', 'un.first_name', 'un.last_name', 'tt.name')
        ->where([
          ['u.email', '=', $request->input('email')]
        ])->whereNull('u.deleted_at')->first();

      Log::debug('D');

      DB::beginTransaction();
      if ($user) {
        Log::debug('E');

        // deactivate current role if any
        DB::table('client_users')->where([
          ['status', '=', 1], ['user_id', '=', $user->id], ['client_id', '=', $request->input('company_id')]
        ])
          ->update([
            'status' => 0, 'updated_at' => now()
          ]);

        Log::debug('F');

        // add to client user
        DB::table('client_users')->insert([
          'client_id' => $request->input('company_id'), 'role_id' => $request->input('role_id'),
          'user_id' => $user->id, 'created_at' => now(), 'updated_at' => now()
        ]);

        Log::debug('G');

        // notify
        Mail::to($user->email)->send(new ClientUserAssignment(
          $inviter->first_name . ' ' . $inviter->last_name,
          $user->first_name . ' ' . $user->last_name,
          $role->name,
          $company['name']
        ));
      } else {
        Log::debug(1);

        $cuToken = Str::uuid() . '-' . time();
        $invToken = Str::uuid() . '-' . time();
        $expData = date_add(new DateTime(), date_interval_create_from_date_string('7 days'));

        Log::debug(2);

        // deactivate any existing invite for the same company
        DB::table('client_user_invites')->where([
          ['status', '=', 1], ['email', '=', $request->input('email')], ['client_id', '=', $request->input('company_id')]
        ])->update([
          'status' => 0, 'updated_at' => now()
        ]);

        Log::debug(3);

        DB::table('registration_invites')->where([
          ['status', '=', 1], ['email', '=', $request->input('email')]
        ])->update([
          'status' => 0, 'updated_at' => now()
        ]);

        Log::debug(4);

        // create invite as client user
        DB::table('client_user_invites')->insert([
          'client_id' => $company['id'], 'role_id' => $role->id, 'email' => $request->input('email'), 'token' => $cuToken,
          'status' => 1, 'expired_at' => $expData, 'created_at' => now(), 'updated_at' => now()
        ]);

        Log::debug(5);

        // create invite as user
        DB::table('registration_invites')->insert([
          'email' => $request->input('email'), 'token' => $invToken,
          'status' => 1, 'expired_at' => $expData, 'created_at' => now(), 'updated_at' => now()
        ]);

        Log::debug(6);

        // notify for both
        Mail::to($request->input('email'))->send(new UserInvitation(
          config('app.url') . '/auth/activate/' . Crypt::encrypt($invToken)
        ));
        Mail::to($request->input('email'))->send(new ClientUserInvitation(
          $inviter->first_name . ' ' . $inviter->last_name,
          $role->name,
          $company['name']
        ));

        Log::debug(7);
      }
      DB::commit();

      return response()->json([
        'success' => true,
        'message' => 'Invite successully sent'
      ]);
    } catch (Exception $ex) {
      DB::rollBack();
      Log::error($ex);
      return response()->json([
        'success' => false,
        'message' => 'Error processing data'
      ]);
    }
  }

  /**
   * for one or more companies in one go. require
   * - user selection of source of email
   * - user reviewing and confirming
   */
  public function batchInviteMember(Request $request)
  {
  }

  // WS

  public function informPublic(Request $request)
  {
    $validator = Validator::make($request->all(), [
      'message' => 'required|string',
      'message_type' => 'required|string',
    ]);

    if ($validator->fails()) {
      return response()->json([
        'success' => false,
        'message' => 'Invalid data provided'
      ]);
    }

    try {
      AppMain::dispatch(
        $request->input('message'),
        $request->input('message_type')
      );

      return response()->json([
        'success' => true,
        'message' => 'OK'
      ]);
    } catch (Exception $ex) {
      Log::error('error processing data: ' . $ex);
      return response()->json([
        'success' => false,
        'message' => 'Error processing data: ' . $ex
      ]);
    }
  }

  public function informPublic2(Request $request)
  {
    Log::debug('okokok');
    try {
      AppMain::dispatch('Some text', 'info');
      dd('ok');
    } catch (Exception $ex) {
      Log::error('error processing data: ' . $ex);
      dd('error');
    }
  }

  public function refreshUser(Request $request)
  {
    $validator = Validator::make($request->all(), [
      'user_id' => 'required|exists:users,id',
    ]);

    if ($validator->fails()) {
      return response()->json([
        'success' => false,
        'message' => 'Invalid data provided'
      ]);
    }

    try {
      SystemRefreshUser::dispatch($request->input('user_id'));

      return response()->json([
        'success' => true,
        'message' => 'OK'
      ]);
    } catch (Exception $ex) {
      Log::error('error processing data: ' . $ex);
      return response()->json([
        'success' => false,
        'message' => 'Error processing data: ' . $ex
      ]);
    }
  }

  public function kickUser(Request $request)
  {
    $validator = Validator::make($request->all(), [
      'user_id' => 'required|exists:users,id',
    ]);

    if ($validator->fails()) {
      return response()->json([
        'success' => false,
        'message' => 'Invalid data provided'
      ]);
    }

    try {
      SystemKickUser::dispatch($request->input('user_id'));

      return response()->json([
        'success' => true,
        'message' => 'OK'
      ]);
    } catch (Exception $ex) {
      Log::error('error processing data: ' . $ex);
      return response()->json([
        'success' => false,
        'message' => 'Error processing data: ' . $ex
      ]);
    }
  }
}
