<?php

namespace App\Http\Controllers;

use App\Events\InvoiceInvitationWasViewed;
use App\Events\QuoteInvitationWasViewed;
use App\Models\Account;
use App\Models\Contact;
use App\Models\Document;
use App\Models\Gateway;
use App\Models\Invitation;
use App\Models\PaymentMethod;
use App\Ninja\Repositories\ActivityRepository;
use App\Ninja\Repositories\CreditRepository;
use App\Ninja\Repositories\DocumentRepository;
use App\Ninja\Repositories\InvoiceRepository;
use App\Ninja\Repositories\PaymentRepository;
use App\Ninja\Repositories\TaskRepository;
use App\Services\PaymentService;
use App\Jobs\Client\GenerateStatementData;
use Auth;
use Barracuda\ArchiveStream\ZipArchive;
use Cache;
use Datatable;
use Exception;
use Input;
use Redirect;
use Request;
use Response;
use Session;
use URL;
use Utils;
use Validator;
use View;

class ClientPortalController extends BaseController
{
    private $invoiceRepo;
    private $paymentRepo;
    private $documentRepo;

    public function __construct(
        InvoiceRepository $invoiceRepo,
        PaymentRepository $paymentRepo,
        ActivityRepository $activityRepo,
        DocumentRepository $documentRepo,
        PaymentService $paymentService,
        CreditRepository $creditRepo,
        TaskRepository $taskRepo)
    {
        $this->invoiceRepo = $invoiceRepo;
        $this->paymentRepo = $paymentRepo;
        $this->activityRepo = $activityRepo;
        $this->documentRepo = $documentRepo;
        $this->paymentService = $paymentService;
        $this->creditRepo = $creditRepo;
        $this->taskRepo = $taskRepo;
    }

    public function viewInvoice($invitationKey)
    {
        if (! $invitation = $this->invoiceRepo->findInvoiceByInvitation($invitationKey)) {
            return $this->returnError();
        }

        $invoice = $invitation->invoice;
        $client = $invoice->client;
        $account = $invoice->account;

        /* Forward requests from V4 to V5 if the domain is set */
        if(strlen($account->account_email_settings->forward_url_for_v5) >1){

            if ($invoice->isType(INVOICE_TYPE_QUOTE)) 
                $entity = 'quote';
            else
                $entity = 'invoice';

            return redirect($account->account_email_settings->forward_url_for_v5."/client/".$entity."/".$invitationKey);
        }

        if (request()->silent) {
            session(['silent:' . $client->id => true]);
            return redirect(request()->url() . (request()->borderless ? '?borderless=true' : ''));
        }

        if (! $account->checkSubdomain(Request::server('HTTP_HOST'))) {
            return response()->view('error', [
                'error' => trans('texts.invoice_not_found'),
            ]);
        }

        if (! Request::has('phantomjs') && ! session('silent:' . $client->id) && ! Session::has($invitation->invitation_key)
            && (! Auth::check() || Auth::user()->account_id != $invoice->account_id)) {
            if ($invoice->isType(INVOICE_TYPE_QUOTE)) {
                event(new QuoteInvitationWasViewed($invoice, $invitation));
            } else {
                event(new InvoiceInvitationWasViewed($invoice, $invitation));
            }
        }

        Session::put($invitation->invitation_key, true); // track this invitation has been seen
        Session::put('contact_key', $invitation->contact->contact_key); // track current contact

        $invoice->invoice_date = Utils::fromSqlDate($invoice->invoice_date);
        $invoice->due_date = Utils::fromSqlDate($invoice->due_date);
        $invoice->partial_due_date = Utils::fromSqlDate($invoice->partial_due_date);
        $invoice->features = [
            'customize_invoice_design' => $account->hasFeature(FEATURE_CUSTOMIZE_INVOICE_DESIGN),
            'remove_created_by' => $account->hasFeature(FEATURE_REMOVE_CREATED_BY),
            'invoice_settings' => $account->hasFeature(FEATURE_INVOICE_SETTINGS),
        ];
        $invoice->invoice_fonts = $account->getFontsData();

        if ($design = $account->getCustomDesign($invoice->invoice_design_id)) {
            $invoice->invoice_design->javascript = $design;
        } else {
            $invoice->invoice_design->javascript = $invoice->invoice_design->pdfmake;
        }
        $contact = $invitation->contact;
        $contact->setVisible([
            'first_name',
            'last_name',
            'email',
            'phone',
            'custom_value1',
            'custom_value2',
        ]);
        $account->load(['date_format', 'datetime_format']);

        // translate the country names
        if ($invoice->client->country) {
            $invoice->client->country->name = $invoice->client->country->getName();
        }
        if ($invoice->account->country) {
            $invoice->account->country->name = $invoice->account->country->getName();
        }

        $data = [];
        $paymentTypes = $this->getPaymentTypes($account, $client, $invitation);
        $paymentURL = '';
        if (count($paymentTypes) == 1) {
            $paymentURL = $paymentTypes[0]['url'];
            if (array_key_exists('gatewayTypeId', $paymentTypes[0]) && in_array($paymentTypes[0]['gatewayTypeId'], [GATEWAY_TYPE_CUSTOM1, GATEWAY_TYPE_CUSTOM2, GATEWAY_TYPE_CUSTOM3])) {
                // do nothing
            } elseif (! $account->isGatewayConfigured(GATEWAY_PAYPAL_EXPRESS)) {
                $paymentURL = URL::to($paymentURL);
            }
        }

        if (! Request::has('phantomjs')) {
            if ($wepayGateway = $account->getGatewayConfig(GATEWAY_WEPAY)) {
                $data['enableWePayACH'] = $wepayGateway->getAchEnabled();
            }
            if ($stripeGateway = $account->getGatewayConfig(GATEWAY_STRIPE)) {
                //$data['enableStripeSources'] = $stripeGateway->getAlipayEnabled();
                $data['enableStripeSources'] = true;
            }
        }

        $showApprove = $invoice->quote_invoice_id ? false : true;
        if ($invoice->invoice_status_id >= INVOICE_STATUS_APPROVED) {
            $showApprove = false;
        }

        $gatewayTypeIdCast = false;

        if(count($paymentTypes) >= 1 && array_key_exists('gatewayTypeId', $paymentTypes[0]))
            $gatewayTypeIdCast = $paymentTypes[0]['gatewayTypeId'];

        $data += [
            'account' => $account,
            'showApprove' => $showApprove,
            'showBreadcrumbs' => false,
            'invoice' => $invoice->hidePrivateFields(),
            'invitation' => $invitation,
            'invoiceLabels' => $account->getInvoiceLabels(),
            'contact' => $contact,
            'paymentTypes' => $paymentTypes,
            'paymentURL' => $paymentURL,
            'phantomjs' => Request::has('phantomjs'),
            'gatewayTypeId' => count($paymentTypes) == 1 ? $gatewayTypeIdCast : false,
        ];

        if ($invoice->canBePaid()) {
            if ($paymentDriver = $account->paymentDriver($invitation, GATEWAY_TYPE_CREDIT_CARD)) {
                $data += [
                    'transactionToken' => $paymentDriver->createTransactionToken(),
                    'partialView' => $paymentDriver->partialView(),
                    'accountGateway' => $paymentDriver->accountGateway,
                ];
            }
        }

        if ($account->hasFeature(FEATURE_DOCUMENTS) && $this->canCreateZip()) {
            $zipDocs = $this->getInvoiceZipDocuments($invoice, $size);

            if (count($zipDocs) > 1) {
                $data['documentsZipURL'] = URL::to("client/documents/{$invitation->invitation_key}");
                $data['documentsZipSize'] = $size;
            }
        }

        return View::make(request()->borderless ? 'invoices.view_borderless' : 'invoices.view', $data);
    }

    private function getPaymentTypes($account, $client, $invitation)
    {
        $links = [];

        foreach ($account->account_gateways as $accountGateway) {
            $paymentDriver = $accountGateway->paymentDriver($invitation);
            $links = array_merge($links, $paymentDriver->tokenLinks());
            $links = array_merge($links, $paymentDriver->paymentLinks());
        }

        return $links;
    }

    public function download($invitationKey)
    {
        if (! $invitation = $this->invoiceRepo->findInvoiceByInvitation($invitationKey)) {
            return response()->view('error', [
                'error' => trans('texts.invoice_not_found'),
                'hideHeader' => true,
            ]);
        }

        $invoice = $invitation->invoice;
        $decode = ! request()->base64;
        $pdfString = $invoice->getPDFString($invitation, $decode);

        header('Content-Type: application/pdf');
        header('Content-Length: ' . strlen($pdfString));
        header('Content-disposition: attachment; filename="' . $invoice->getFileName() . '"');
        header('Cache-Control: public, must-revalidate, max-age=0');
        header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');

        return $pdfString;
    }

    public function authorizeInvoice($invitationKey)
    {
        if (! $invitation = $this->invoiceRepo->findInvoiceByInvitation($invitationKey)) {
            return RESULT_FAILURE;
        }

        if ($signature = \Request::input('signature')) {
            $invitation->signature_base64 = $signature;
            $invitation->signature_date = date_create();
            $invitation->save();
        }

        session(['authorized:' . $invitation->invitation_key => true]);

        return RESULT_SUCCESS;
    }

    public function dashboard($contactKey = false)
    {
        if ($contactKey) {
            if (! $contact = Contact::where('contact_key', '=', $contactKey)->first()) {
                return $this->returnError();
            }
            Session::put('contact_key', $contactKey); // track current contact
        } elseif (! $contact = $this->getContact()) {
            return $this->returnError();
        }

        $client = $contact->client;
        $account = $client->account;

        if (request()->silent) {
            session(['silent:' . $client->id => true]);
            return redirect(request()->url());
        }

        $color = $account->primary_color ? $account->primary_color : '#0b4d78';
        $customer = false;

        if (! $account->enable_client_portal) {
            return $this->returnError();
        } elseif (! $account->enable_client_portal_dashboard) {
            session()->reflash();
            return redirect()->to('/client/invoices/');
        }

        if ($paymentDriver = $account->paymentDriver(false, GATEWAY_TYPE_TOKEN)) {
            $customer = $paymentDriver->customer($client->id);
        }

        $data = [
            'color' => $color,
            'contact' => $contact,
            'account' => $account,
            'client' => $client,
            'gateway' => $account->getTokenGateway(),
            'paymentMethods' => $customer ? $customer->payment_methods : false,
            'transactionToken' => $paymentDriver ? $paymentDriver->createTransactionToken() : false,
        ];

        return response()->view('invited.dashboard', $data);
    }

    public function activityDatatable()
    {
        if (! $contact = $this->getContact()) {
            return $this->returnError();
        }

        $client = $contact->client;

        $query = $this->activityRepo->findByClientId($client->id);
        $query->where('activities.adjustment', '!=', 0);

        return Datatable::query($query)
            ->addColumn('activities.id', function ($model) {
                return Utils::timestampToDateTimeString(strtotime($model->created_at));
            })
            ->addColumn('activity_type_id', function ($model) {
                $data = [
                    'client' => Utils::getClientDisplayName($model),
                    'user' => $model->is_system ? ('<i>' . trans('texts.system') . '</i>') : ($model->account_name),
                    'invoice' => $model->invoice,
                    'contact' => Utils::getClientDisplayName($model),
                    'payment' => $model->payment ? ' ' . $model->payment : '',
                    'credit' => $model->payment_amount ? Utils::formatMoney($model->credit, $model->currency_id, $model->country_id) : '',
                    'payment_amount' => $model->payment_amount ? Utils::formatMoney($model->payment_amount, $model->currency_id, $model->country_id) : null,
                    'adjustment' => $model->adjustment ? Utils::formatMoney($model->adjustment, $model->currency_id, $model->country_id) : null,
                ];

                return trans("texts.activity_{$model->activity_type_id}", $data);
            })
            ->addColumn('balance', function ($model) {
                return Utils::formatMoney($model->balance, $model->currency_id, $model->country_id);
            })
            ->addColumn('adjustment', function ($model) {
                return $model->adjustment != 0 ? Utils::wrapAdjustment($model->adjustment, $model->currency_id, $model->country_id) : '';
            })
            ->make();
    }

    public function recurringInvoiceIndex()
    {
        if (! $contact = $this->getContact()) {
            return $this->returnError();
        }

        $account = $contact->account;

        if (! $account->enable_client_portal) {
            return $this->returnError();
        }

        $color = $account->primary_color ? $account->primary_color : '#0b4d78';
        $columns = ['frequency', 'start_date', 'end_date', 'invoice_total'];
        $client = $contact->client;

        if ($client->hasAutoBillConfigurableInvoices()) {
            $columns[] = 'auto_bill';
        }

        $data = [
            'color' => $color,
            'account' => $account,
            'client' => $client,
            'title' => trans('texts.recurring_invoices'),
            'entityType' => ENTITY_RECURRING_INVOICE,
            'columns' => Utils::trans($columns),
            'sortColumn' => 1,
        ];

        return response()->view('public_list', $data);
    }

    public function invoiceIndex()
    {
        if (! $contact = $this->getContact()) {
            return $this->returnError();
        }

        $account = $contact->account;

        if (! $account->enable_client_portal) {
            return $this->returnError();
        }

        $color = $account->primary_color ? $account->primary_color : '#0b4d78';

        $data = [
            'color' => $color,
            'account' => $account,
            'client' => $contact->client,
            'title' => trans('texts.invoices'),
            'entityType' => ENTITY_INVOICE,
            'columns' => Utils::trans(['invoice_number', 'invoice_date', 'invoice_total', 'balance_due', 'due_date', 'status']),
            'sortColumn' => 1,
        ];

        return response()->view('public_list', $data);
    }

    public function invoiceDatatable()
    {
        if (! $contact = $this->getContact()) {
            return '';
        }

        return $this->invoiceRepo->getClientDatatable($contact->id, ENTITY_INVOICE, \Request::input('sSearch'));
    }

    public function recurringInvoiceDatatable()
    {
        if (! $contact = $this->getContact()) {
            return '';
        }

        return $this->invoiceRepo->getClientRecurringDatatable($contact->id);
    }

    public function paymentIndex()
    {
        if (! $contact = $this->getContact()) {
            return $this->returnError();
        }

        $account = $contact->account;

        if (! $account->enable_client_portal) {
            return $this->returnError();
        }

        $color = $account->primary_color ? $account->primary_color : '#0b4d78';

        $data = [
            'color' => $color,
            'account' => $account,
            'entityType' => ENTITY_PAYMENT,
            'title' => trans('texts.payments'),
            'columns' => Utils::trans(['invoice', 'transaction_reference', 'method', 'payment_amount', 'payment_date', 'status']),
            'sortColumn' => 4,
        ];

        return response()->view('public_list', $data);
    }

    public function paymentDatatable()
    {
        if (! $contact = $this->getContact()) {
            return $this->returnError();
        }
        $payments = $this->paymentRepo->findForContact($contact->id, \Request::input('sSearch'));

        return Datatable::query($payments)
                ->addColumn('invoice_number', function ($model) {
                    return $model->invitation_key ? link_to('/view/'.$model->invitation_key, $model->invoice_number)->toHtml() : $model->invoice_number;
                })
                ->addColumn('transaction_reference', function ($model) {
                    return $model->transaction_reference ? e($model->transaction_reference) : '<i>'.trans('texts.manual_entry').'</i>';
                })
                ->addColumn('payment_type', function ($model) {
                    return ($model->payment_type && ! $model->last4) ? $model->payment_type : ($model->account_gateway_id ? '<i>Online payment</i>' : '');
                })
                ->addColumn('amount', function ($model) {
                    return Utils::formatMoney($model->amount, $model->currency_id, $model->country_id);
                })
                ->addColumn('payment_date', function ($model) {
                    return Utils::dateToString($model->payment_date);
                })
                ->addColumn('status', function ($model) {
                    return $this->getPaymentStatusLabel($model);
                })
                ->orderColumns('invoice_number', 'transaction_reference', 'payment_type', 'amount', 'payment_date')
                ->make();
    }

    private function getPaymentStatusLabel($model)
    {
        $label = trans('texts.status_' . strtolower($model->payment_status_name));
        $class = 'default';
        switch ($model->payment_status_id) {
            case PAYMENT_STATUS_PENDING:
                $class = 'info';
                break;
            case PAYMENT_STATUS_COMPLETED:
                $class = 'success';
                break;
            case PAYMENT_STATUS_FAILED:
                $class = 'danger';
                break;
            case PAYMENT_STATUS_PARTIALLY_REFUNDED:
                $label = trans('texts.status_partially_refunded_amount', [
                    'amount' => Utils::formatMoney($model->refunded, $model->currency_id, $model->country_id),
                ]);
                $class = 'primary';
                break;
            case PAYMENT_STATUS_REFUNDED:
                $class = 'default';
                break;
        }

        return "<h4><div class=\"label label-{$class}\">$label</div></h4>";
    }

    public function quoteIndex()
    {
        if (! $contact = $this->getContact()) {
            return $this->returnError();
        }

        $account = $contact->account;

        if (! $account->enable_client_portal) {
            return $this->returnError();
        }

        $color = $account->primary_color ? $account->primary_color : '#0b4d78';

        $data = [
          'color' => $color,
          'account' => $account,
          'title' => trans('texts.quotes'),
          'entityType' => ENTITY_QUOTE,
          'columns' => Utils::trans(['quote_number', 'quote_date', 'quote_total', 'due_date', 'status']),
          'sortColumn' => 1,
        ];

        return response()->view('public_list', $data);
    }

    public function quoteDatatable()
    {
        if (! $contact = $this->getContact()) {
            return false;
        }

        return $this->invoiceRepo->getClientDatatable($contact->id, ENTITY_QUOTE, \Request::input('sSearch'));
    }

    public function creditIndex()
    {
        if (! $contact = $this->getContact()) {
            return $this->returnError();
        }

        $account = $contact->account;

        if (! $account->enable_client_portal) {
            return $this->returnError();
        }

        $color = $account->primary_color ? $account->primary_color : '#0b4d78';

        $data = [
          'color' => $color,
          'account' => $account,
          'title' => trans('texts.credits'),
          'entityType' => ENTITY_CREDIT,
          'columns' => Utils::trans(['credit_date', 'credit_amount', 'credit_balance', 'notes']),
          'sortColumn' => 0,
        ];

        return response()->view('public_list', $data);
    }

    public function creditDatatable()
    {
        if (! $contact = $this->getContact()) {
            return false;
        }

        return $this->creditRepo->getClientDatatable($contact->client_id);
    }

    public function taskIndex()
    {
        if (! $contact = $this->getContact()) {
            return $this->returnError();
        }

        $account = $contact->account;

        if (! $contact->client->show_tasks_in_portal) {
            return redirect()->to($account->enable_client_portal_dashboard ? '/client/dashboard' : '/client/payment_methods/');
        }

        if (! $account->enable_client_portal) {
            return $this->returnError();
        }

        $color = $account->primary_color ? $account->primary_color : '#0b4d78';

        $data = [
          'color' => $color,
          'account' => $account,
          'title' => trans('texts.tasks'),
          'entityType' => ENTITY_TASK,
          'columns' => Utils::trans(['project', 'date', 'duration', 'description']),
          'sortColumn' => 1,
        ];

        return response()->view('public_list', $data);
    }

    public function taskDatatable()
    {
        if (! $contact = $this->getContact()) {
            return false;
        }

        return $this->taskRepo->getClientDatatable($contact->client_id);
    }

    public function documentIndex()
    {
        if (! $contact = $this->getContact()) {
            return $this->returnError();
        }

        $account = $contact->account;

        if (! $account->enable_client_portal) {
            return $this->returnError();
        }

        $color = $account->primary_color ? $account->primary_color : '#0b4d78';

        $data = [
          'color' => $color,
          'account' => $account,
          'title' => trans('texts.documents'),
          'entityType' => ENTITY_DOCUMENT,
          'columns' => Utils::trans(['invoice_number', 'name', 'document_date', 'document_size']),
          'sortColumn' => 2,
        ];

        return response()->view('public_list', $data);
    }

    public function documentDatatable()
    {
        if (! $contact = $this->getContact()) {
            return false;
        }

        return $this->documentRepo->getClientDatatable($contact->id, ENTITY_DOCUMENT, \Request::input('sSearch'));
    }

    private function returnError($error = false)
    {
        if (request()->phantomjs) {
            abort(404);
        }

        return response()->view('error', [
            'error' => $error ?: trans('texts.invoice_not_found'),
            'hideHeader' => true,
            'account' => $this->getContact() ? $this->getContact()->account : false,
        ]);
    }

    private function getContact()
    {
        $contactKey = session('contact_key');

        if (! $contactKey) {
            return false;
        }

        $contact = Contact::where('contact_key', '=', $contactKey)->first();

        if (! $contact || $contact->is_deleted) {
            return false;
        }

        return $contact;
    }

    public function getDocumentVFSJS($publicId, $name)
    {
        if (! $contact = $this->getContact()) {
            return $this->returnError();
        }

        $document = Document::scope($publicId, $contact->account_id)->first();

        if (! $document->isPDFEmbeddable()) {
            return Response::view('error', ['error' => 'Image does not exist!'], 404);
        }

        $authorized = false;
        if ($document->expense && $document->expense->client_id == $contact->client_id) {
            $authorized = true;
        } elseif ($document->invoice && $document->invoice->client_id == $contact->client_id) {
            $authorized = true;
        }

        if (! $authorized) {
            return Response::view('error', ['error' => 'Not authorized'], 403);
        }

        if (substr($name, -3) == '.js') {
            $name = substr($name, 0, -3);
        }

        $content = $document->preview ? $document->getRawPreview() : $document->getRaw();
        $content = 'ninjaAddVFSDoc('.json_encode(intval($publicId).'/'.strval($name)).',"'.base64_encode($content).'")';
        $response = Response::make($content, 200);
        $response->header('content-type', 'text/javascript');
        $response->header('cache-control', 'max-age=31536000');

        return $response;
    }

    protected function canCreateZip()
    {
        return function_exists('gmp_init');
    }

    protected function getInvoiceZipDocuments($invoice, &$size = 0)
    {
        $documents = $invoice->documents;

        foreach ($invoice->expenses as $expense) {
            if ($expense->invoice_documents) {
                $documents = $documents->merge($expense->documents);
            }
        }

        $documents = $documents->sortBy('size');

        $size = 0;
        $maxSize = MAX_ZIP_DOCUMENTS_SIZE * 1000;
        $toZip = [];
        foreach ($documents as $document) {
            if ($size + $document->size > $maxSize) {
                break;
            }

            if (! empty($toZip[$document->name])) {
                // This name is taken
                if ($toZip[$document->name]->hash != $document->hash) {
                    // 2 different files with the same name
                    $nameInfo = pathinfo($document->name);

                    for ($i = 1; ; $i++) {
                        $name = $nameInfo['filename'].' ('.$i.').'.$nameInfo['extension'];

                        if (empty($toZip[$name])) {
                            $toZip[$name] = $document;
                            $size += $document->size;
                            break;
                        } elseif ($toZip[$name]->hash == $document->hash) {
                            // We're not adding this after all
                            break;
                        }
                    }
                }
            } else {
                $toZip[$document->name] = $document;
                $size += $document->size;
            }
        }

        return $toZip;
    }

    public function getInvoiceDocumentsZip($invitationKey)
    {
        if (! $invitation = $this->invoiceRepo->findInvoiceByInvitation($invitationKey)) {
            return $this->returnError();
        }

        Session::put('contact_key', $invitation->contact->contact_key); // track current contact

        $invoice = $invitation->invoice;

        $toZip = $this->getInvoiceZipDocuments($invoice);

        if (! count($toZip)) {
            return Response::view('error', ['error' => 'No documents small enough'], 404);
        }

        $zip = new ZipArchive($invitation->account->name.' Invoice '.$invoice->invoice_number.'.zip');

        return Response::stream(function () use ($toZip, $zip) {
            foreach ($toZip as $name => $document) {
                $fileStream = $document->getStream();
                if ($fileStream) {
                    $zip->init_file_stream_transfer($name, $document->size, ['time' => $document->created_at->timestamp]);
                    while ($buffer = fread($fileStream, 256000)) {
                        $zip->stream_file_part($buffer);
                    }
                    fclose($fileStream);
                    $zip->complete_file_stream();
                } else {
                    $zip->add_file($name, $document->getRaw());
                }
            }
            $zip->finish();
        }, 200);
    }

    public function getDocument($invitationKey, $publicId)
    {
        if (! $invitation = $this->invoiceRepo->findInvoiceByInvitation($invitationKey)) {
            return $this->returnError();
        }

        Session::put('contact_key', $invitation->contact->contact_key); // track current contact

        $clientId = $invitation->invoice->client_id;
        $document = Document::scope($publicId, $invitation->account_id)->firstOrFail();

        $authorized = false;
        if ($document->is_default) {
            $authorized = true;
        } elseif ($document->expense && $document->expense->invoice_documents && $document->expense->client_id == $invitation->invoice->client_id) {
            $authorized = true;
        } elseif ($document->invoice && $document->invoice->client_id == $invitation->invoice->client_id) {
            $authorized = true;
        }

        if (! $authorized) {
            return Response::view('error', ['error' => 'Not authorized'], 403);
        }

        return DocumentController::getDownloadResponse($document);
    }

    public function paymentMethods()
    {
        if (! $contact = $this->getContact()) {
            return $this->returnError();
        }

        $client = $contact->client;
        $account = $client->account;

        $paymentDriver = $account->paymentDriver(false, GATEWAY_TYPE_TOKEN);
        $customer = $paymentDriver->customer($client->id);

        $data = [
            'account' => $account,
            'contact' => $contact,
            'color' => $account->primary_color ? $account->primary_color : '#0b4d78',
            'client' => $client,
            'paymentMethods' => $customer ? $customer->payment_methods : false,
            'gateway' => $account->getTokenGateway(),
            'title' => trans('texts.payment_methods'),
            'transactionToken' => $paymentDriver->createTransactionToken(),
        ];

        return response()->view('payments.paymentmethods', $data);
    }

    public function verifyPaymentMethod()
    {
        $publicId = \Request::input('source_id');
        $amount1 = \Request::input('verification1');
        $amount2 = \Request::input('verification2');

        if (! $contact = $this->getContact()) {
            return $this->returnError();
        }

        $client = $contact->client;
        $account = $client->account;

        $paymentDriver = $account->paymentDriver(null, GATEWAY_TYPE_BANK_TRANSFER);
        $result = $paymentDriver->verifyBankAccount($client, $publicId, $amount1, $amount2);

        if (is_string($result)) {
            Session::flash('error', $result);
        } else {
            Session::flash('message', trans('texts.payment_method_verified'));
        }

        return redirect()->to($account->enable_client_portal_dashboard ? '/client/dashboard' : '/client/payment_methods/');
    }

    public function removePaymentMethod($publicId)
    {
        if (! $contact = $this->getContact()) {
            return $this->returnError();
        }

        $client = $contact->client;
        $account = $contact->account;

        $paymentDriver = $account->paymentDriver(false, GATEWAY_TYPE_TOKEN);
        $paymentMethod = PaymentMethod::clientId($client->id)
            ->wherePublicId($publicId)
            ->firstOrFail();

        try {
            $paymentDriver->removePaymentMethod($paymentMethod);
            Session::flash('message', trans('texts.payment_method_removed'));
        } catch (Exception $exception) {
            Session::flash('error', $exception->getMessage());
        }

        return redirect()->to($client->account->enable_client_portal_dashboard ? '/client/dashboard' : '/client/payment_methods/');
    }

    public function setDefaultPaymentMethod()
    {
        if (! $contact = $this->getContact()) {
            return $this->returnError();
        }

        $client = $contact->client;
        $account = $client->account;

        $validator = Validator::make(Request::all(), ['source' => 'required']);
        if ($validator->fails()) {
            return Redirect::to($client->account->enable_client_portal_dashboard ? '/client/dashboard' : '/client/payment_methods/');
        }

        $paymentDriver = $account->paymentDriver(false, GATEWAY_TYPE_TOKEN);
        $paymentMethod = PaymentMethod::clientId($client->id)
            ->wherePublicId(\Request::input('source'))
            ->firstOrFail();

        $customer = $paymentDriver->customer($client->id);
        $customer->default_payment_method_id = $paymentMethod->id;
        $customer->save();

        Session::flash('message', trans('texts.payment_method_set_as_default'));

        return redirect()->to($client->account->enable_client_portal_dashboard ? '/client/dashboard' : '/client/payment_methods/');
    }

    private function paymentMethodError($type, $error, $accountGateway = false, $exception = false)
    {
        $message = '';
        if ($accountGateway && $accountGateway->gateway) {
            $message = $accountGateway->gateway->name . ': ';
        }
        $message .= $error ?: trans('texts.payment_method_error');

        Session::flash('error', $message);
        Utils::logError("Payment Method Error [{$type}]: " . ($exception ? Utils::getErrorString($exception) : $message), 'PHP', true);
    }

    public function setAutoBill()
    {
        if (! $contact = $this->getContact()) {
            return $this->returnError();
        }

        $client = $contact->client;

        $validator = Validator::make(Request::all(), ['public_id' => 'required']);

        if ($validator->fails()) {
            return Redirect::to('client/invoices/recurring');
        }

        $publicId = \Request::input('public_id');
        $enable = \Request::input('enable');
        $invoice = $client->invoices()->where('public_id', intval($publicId))->first();

        if ($invoice && $invoice->is_recurring && ($invoice->auto_bill == AUTO_BILL_OPT_IN || $invoice->auto_bill == AUTO_BILL_OPT_OUT)) {
            $invoice->client_enable_auto_bill = $enable ? true : false;
            $invoice->save();
        }

        return Redirect::to('client/invoices/recurring');
    }

    public function showDetails()
    {
        if (! $contact = $this->getContact()) {
            return $this->returnError();
        }

        $data = [
            'contact' => $contact,
            'client' => $contact->client,
            'account' => $contact->account,
        ];

        return view('invited.details', $data);
    }

    public function updateDetails(\Illuminate\Http\Request $request)
    {
        if (! $contact = $this->getContact()) {
            return $this->returnError();
        }

        $client = $contact->client;
        $account = $contact->account;

        if (! $account->enable_client_portal) {
            return $this->returnError();
        }

        $rules = [
            'email' => 'required',
            'address1' => 'required',
            'city' => 'required',
            'state' => $account->requiresAddressState() ? 'required' : '',
            'postal_code' => 'required',
            'country_id' => 'required',
        ];

        if ($client->name) {
            $rules['name'] = 'required';
        } else {
            $rules['first_name'] = 'required';
            $rules['last_name'] = 'required';
        }
        if ($account->vat_number || $account->isNinjaAccount()) {
            $rules['vat_number'] = 'required';
        }

        $this->validate($request, $rules);

        $contact->fill(request()->all());
        $contact->save();

        $client->fill(request()->all());
        $client->save();

        event(new \App\Events\ClientWasUpdated($client));

        return redirect($account->enable_client_portal_dashboard ? '/client/dashboard' : '/client/payment_methods')
            ->withMessage(trans('texts.updated_client_details'));
    }

    public function statement() {
        if (! $contact = $this->getContact()) {
            return $this->returnError();
        }

        $client = $contact->client;
        $account = $contact->account;

        if (! $account->enable_client_portal || ! $account->enable_client_portal_dashboard) {
            return $this->returnError();
        }

        $statusId = request()->status_id;
        $startDate = request()->start_date;
        $endDate = request()->end_date;

        if (! $startDate) {
            $startDate = Utils::today(false)->modify('-6 month')->format('Y-m-d');
            $endDate = Utils::today(false)->format('Y-m-d');
        }

        if (request()->json) {
            return dispatch_now(new GenerateStatementData($client, request()->all(), $contact));
        }

        $data = [
            'extends' => 'public.header',
            'client' => $client,
            'account' => $account,
            'startDate' => $startDate,
            'endDate' => $endDate,
        ];

        return view('clients.statement', $data);

    }
}
