<?php

namespace apexl\Io\modules\invoice\providers;

use apexl\Config\Singleton;
use apexl\Io\includes\Hook;
use apexl\Io\includes\System;
use apexl\Io\modules\invoice\services\invoiceService;
use apexl\Io\modules\payment\entities\paymentLogEntity;
use Calcinai\OAuth2\Client\Provider\Xero;
use XeroPHP\Application as XeroApp;
use XeroPHP\Models\Accounting\Account;
use XeroPHP\Models\Accounting\Address;
use XeroPHP\Models\Accounting\BrandingTheme;
use XeroPHP\Models\Accounting\Contact;
use XeroPHP\Models\Accounting\CreditNote;
use XeroPHP\Models\Accounting\History;
use XeroPHP\Models\Accounting\Invoice;
use XeroPHP\Models\Accounting\Payment;
use XeroPHP\Models\Accounting\Phone;
use XeroPHP\Webhook;
use Psr\Http\Message\ServerRequestInterface as Request;

class xeroProvider implements providerInterface
{

    protected $xero;
    protected $config;
    protected $invoiceService;

    public function __construct(invoiceService $invoiceService)
    {
        $this->config = Singleton::getInstance();
        $this->invoiceService = $invoiceService;
    }

    public function processWebhookRequest(Request $request)
    {
        $this->instantiate();

        $payload = $request->getBody();

        $this->xero->setConfig(['webhook' => ['signing_key' => $this->config->app->invoicing->xero->webhook_secret]]);
        $webhook = new Webhook($this->xero, $payload);
        $signature = $request->getHeader('x-xero-signature')[0] ?? '';

        $processes = [];

        if (!$webhook->validate($signature)) {
            return [401, $processes]; // Unauthorized
        }

        $requestData = json_decode($payload);

        if (isset($requestData->events) && is_iterable($requestData->events)) {
            foreach ($requestData->events as $event) {
                if (isset($event->eventCategory) && isset($event->resourceId)) {
                    $processes[] = [
                        'type' => $event->eventCategory,
                        'provider_id' => $event->resourceId,
                        'provider' => 'xero',
                        'received' => date('Y-m-d H:i:s')
                    ];
                }
            }
        }


        return [200, $processes];
    }

    public function processQueueItem($item)
    {
        $result = false;

        switch ($item->type) {
            case 'INVOICE':
                if ($invoiceEntity = $this->invoiceService->getInvoiceByProviderId($item->provider_id)) { // If invoice exists locally
                    if ($invoice = $this->getInvoice($item->provider_id)) {
                        $markAsPaid = 0;
                        if ($invoiceEntity->paid != '1') {
                            $invoiceEntity->paid = $invoice->AmountDue < 0.01 ? 1 : 0;
                            if ($invoiceEntity->paid == 1) {
                                $this->invoiceService->reenableInvoiceSubscriptions($invoiceEntity);
                                $markAsPaid = 1;
                            }
                        }

                        $invoiceEntity = $this->syncCreditNoteInfo($invoice, $invoiceEntity);

                        $invoiceEntity->store();
                        if ($markAsPaid) Hook::processHook('invoice_marked_as_paid', $invoiceEntity);
                        $result = true;
                    }
                } else {
                    // Check if the provided reference matches Dashboard
                    if ($invoice = $this->getInvoice($item->provider_id)) {
                        if (trim($invoice->Reference) != "") {
                            if ($invoiceEntity = $this->invoiceService->getInvoiceByReference($invoice->Reference)) {
                                if (trim($invoiceEntity->provider_id) == "") {
                                    $invoiceEntity->provider_id = $item->provider_id;
                                    $invoiceEntity = $this->syncCreditNoteInfo($invoice, $invoiceEntity);
                                    $invoiceEntity->store();
                                }
                                $result = true;
                            }
                        }
                    }
                }
                break;
        }

        return $result;
    }

    protected function syncCreditNoteInfo($invoice, $invoiceEntity)
    {
        try {
            if ($invoice->getAmountCredited() > 0) {
                $creditInfo = [];
                foreach ($invoice->getCreditNotes() as $creditNote) {
                    $total = number_format($creditNote->getTotal(), 2);
                    $creditInfo[] = $creditNote->getCreditNoteNumber() . " ({$total})";
                }
                $invoiceEntity->credit_note_info = implode("<br>", $creditInfo);
            }
        } catch (\Exception $e) {
            error_log('Sync Credit Note Failed: ' . $e->getMessage());
        }

        return $invoiceEntity;
    }

    protected function logRegest($log)
    {
        $paymentLog = new paymentLogEntity();
        $paymentLog->ts = date('Y-m-d H:i:s');
        $paymentLog->provider = 'xero';
        $paymentLog->api_name = $log['api_name'];
        $paymentLog->method_name = $log['method_name'];
        if (isset($log['request_data'])) $paymentLog->request_data = json_encode($log['request_data']);
        if (isset($log['response_data'])) $paymentLog->response_data = json_encode($log['response_data']);
        $paymentLog->store();
    }

    public function getInvoices($email, $expanded = false)
    {
        $this->instantiate();

        $contact = $this->getContact((object)['emailAddress' => $email], 'email');

        if (!$contact) return [];

        $data = [];
        $invoices = [];

        try {
            $log = [
                'api_name' => 'Invoice',
                'method_name' => 'load',
                'request_data' => [
                    'Contact.ContactID' => $contact->getContactID()
                ]
            ];
            $invoices = $this->xero->load(Invoice::class)
                ->where('Contact.ContactID', $contact->getContactID())
                ->execute();
        } catch (\Exception $e) {
            $log['response_data'] = [$e->getMessage()];
        }
        $this->logRegest($log);

        if (isset($invoices) && is_iterable($invoices)) {
            foreach ($invoices as $invoice) {
                $dat = [
                    'id' => $invoice->getInvoiceID(),
                    'name' => $invoice->getInvoiceNumber(),
                    'email' => $email,
                    'date' => $invoice->getDate()->format('jS M \'y'),
                    'reference' => $invoice->getReference(),
                    'subtotal' => number_format($invoice->getSubTotal(), 2),
                    'totalTax' => number_format($invoice->getTotalTax(), 2),
                    'total' => number_format($invoice->getTotal(), 2),
                    //$this->getInvoiceUrl($invoice) ?? 'Unavailable',
                ];

                if ($expanded) {
                    $dat['dateMysql'] = $invoice->getDate()->format('Y-m-d');
                    $dat['paid'] = $invoice->getAmountDue() < 0.01 ? 1 : 0;
                    $dat['lineItems'] = [];
                    foreach ($invoice->getLineItems() as $lineItem) {
                        $item = (object)[
                            'description' => $lineItem->getDescription(),
                            'accountCode' => $lineItem->getAccountCode(),
                            'quantity' => $lineItem->getQuantity(),
                            'perUnit' => $lineItem->getUnitAmount(),
                            'amount' => $lineItem->getLineAmount(),
                            'vat' => $lineItem->getTaxAmount(),
                        ];
                        $dat['lineItems'][] = $item;
                    }

                    $dat['lineItems'] = json_encode($dat['lineItems']);
                }

                $data[] = $dat;
            }
        }


        return $data;
    }

    public function getInvoicePDF($invoiceId)
    {
        $content = null;

        try {
            $log = [
                'api_name' => 'Invoice',
                'method_name' => 'getPDF',
                'request_data' => [
                    'InvoiceId' => $invoiceId,
                ]
            ];
            $this->instantiate();
            $content = (new \XeroPHP\Models\Accounting\Invoice)
                ->setGUID($invoiceId)
                ->setApplication($this->xero)
                ->getPDF();
        } catch (\Exception $e) {
            $log['response_data'] = [$e->getMessage()];
        }
        $this->logRegest($log);

        return $content;
    }

    public function getBrandingThemes()
    {
        try {
            $log = [
                'api_name' => 'BrandingTheme',
                'method_name' => 'load',
                'request_data' => [
                ]
            ];
            $themes = (object)[
                'timestamp' => time(),
                'themes' => []
            ];
            $this->instantiate();
            $brandingThemes = $this->xero->load(BrandingTheme::class)->execute();
            foreach ($brandingThemes as $theme) {
                $themes->themes[] = (object)[
                    'name' => $theme->getName(),
                    'id' => $theme->getBrandingThemeID()
                ];
            }
        } catch (\Exception $e) {
            $log['response_data'] = [$e->getMessage()];
        }

        $this->logRegest($log);

        return $themes;
    }

    public function getAccounts()
    {
        $this->instantiate();

        $accounts = $this->xero->load(Account::class)->execute();
        foreach ($accounts as $account) {
            echo "\n" . $account->getName() . ' ' . $account->getCode() . ' ' . $account->getAccountId();
        }

        die();
    }

    public function changeInvoiceContact($invoiceId, $toContactName)
    {
        $invoice = $this->getInvoice($invoiceId);
        if ($invoice) {
            $contact = $this->getContact((object)['name' => $toContactName], 'name');
            if ($contact) {
                echo '<pre>';
                print_r($contact);
                echo '</pre>';
                $invoice->setContact($contact);
                $response = $invoice->save();
                echo '<pre>';
                print_r($response);
                echo '</pre>';
                die();
            }
        }
    }

    public function sendInvoiceEmail($invoiceId)
    {
        $this->instantiate();

        $invoice = $this->getInvoice($invoiceId);
        if ($invoice) {
            try {
                $log = [
                    'api_name' => 'Invoice',
                    'method_name' => 'sendEmail',
                    'request_data' => [
                        'InvoiceId' => $invoiceId,
                    ]
                ];
                $result = $invoice->sendEmail();
            } catch (\Exception $e) {
                $log['response_data'] = [$e->getMessage()];
            }
            $this->logRegest($log);
        }
    }

    public function createInvoice($lineitems, $contactData, $lookupContactBy = 'name', $lineAmountType = 'Inclusive', $dueDate = null, $desc = '', $branding = '', $sendEmail = FALSE, $reference = null, $type = 'ACCREC', $currency = 'GBP')
    {

        $this->instantiate();

        if (!$branding && isset($this->config->app->invoicing->xero->branding_themes->default)) {
            $branding = $this->config->app->invoicing->xero->branding_themes->default;
        }
        if (!$branding) $branding = $this->config->app->invoicing->xero->default_branding_theme ?? '';

        $dueDateTime = new \DateTime("now", new \DateTimeZone('Europe/London'));
        if ($dueDate) $dueDateTime->setTimestamp(strtotime($dueDate));


        $contact = $this->getContact($contactData, $lookupContactBy);

        if (!$contact) $contact = $this->createContact($contactData); //  create a new contact

        $invoice = new \XeroPHP\Models\Accounting\Invoice($this->xero);
        $invoice->setStatus("AUTHORISED");
        $invoice->setLineAmountType($lineAmountType);
        $invoice->setType($type);
        $invoice->setCurrencyCode($currency);
        $invoice->setDueDate($dueDateTime);
        $invoice->setContact($contact);
        $invoice->setBrandingThemeID($branding);
        if ($reference) {
            $invoice->setReference($reference);
        }

        //create the Line items
        foreach ($lineitems as $lineItem) {
            $invoice->addLineItem($this->createInvoiceLineItem($lineItem, $desc));
        }

        try {
            $log = [
                'api_name' => 'Invoice',
                'method_name' => 'save',
                'request_data' => [
                    'Status' => "AUTHORISED",
                    'LineAmountType' => $lineAmountType,
                    'Type' => $type,
                    'CurrencyCode' => $currency,
                    'DueDate' => $dueDateTime->format('Y-m-d'),
                    'Contact' => $contact,
                    'BrandingThemeId' => $branding,
                    'Reference' => $reference,
                ]
            ];
            $response = $invoice->save();
            if ($sendEmail) $invoice->sendEmail();
        } catch (\Exception $e) {
            $log['response_data'] = [$e->getMessage()];
        }
        $this->logRegest($log);

        return [$invoice->getInvoiceId(), $invoice->getInvoiceNumber(), $this->getDataFromContact($contact)];
    }

    public function createPayment($invoiceId, $amount, $paymentRef, $accountId)
    {
        $this->instantiate();

        try {
            $log = [
                'api_name' => 'Payment',
                'method_name' => 'save',
                'request_data' => [
                    'Date' => date('Y-m-d'),
                    'Amount' => $amount,
                    'Reference' => $paymentRef,
                    'Account' => $accountId,
                    'InvoiceId' => $invoiceId,
                    'Status' => 'AUTHORISED'
                ]
            ];

            $payment = (new Payment($this->xero))
                ->setDate(new \DateTime('now'))
                ->setAmount($amount)
                ->setReference($paymentRef)
                ->setAccount(
                    (new Account())->setAccountID($accountId)
                )->setInvoice(
                    (new Invoice())->setInvoiceID($invoiceId)
                )->setStatus('AUTHORISED');

            $response = $payment->save();
            $paymentId = $payment->getPaymentId();
        } catch (\Exception $e) {
            $log['response_data'] = [$e->getMessage()];
            $paymentId = null;
        }
        $this->logRegest($log);

        return $paymentId;
    }

    public function creditInvoice($invoiceId, $contactData, $lookupContactBy)
    {
        if ($invoice = $this->getInvoice($invoiceId)) {
            if ($creditNoteId = $this->createCreditNote($invoice->getLineItems(), $contactData, $lookupContactBy)) {
                $creditNotes = $this->xero->load(CreditNote::class)
                    ->where('CreditNoteID', $creditNoteId)
                    ->execute();
                foreach ($creditNotes as $creditNote) {
                    try {
                        $log = [
                            'api_name' => 'CreditNoteAllocation',
                            'method_name' => 'save',
                            'request_data' => [
                                'Amount' => $invoice->getTotal(),
                                'InvoiceId' => $invoiceId,
                            ]
                        ];

                        $allocation = (new CreditNote\Allocation($this->xero))
                            ->setAppliedAmount($invoice->getTotal())
                            ->setInvoice($invoice)
                            ->setDate(new \DateTime('now', new \DateTimeZone('Europe/London')));

                        $creditNote->addAllocation($allocation);
                        $response = $creditNote->save();

                    } catch (\Exception $e) {
                        $log['response_data'] = [$e->getMessage()];
                    }
                    $this->logRegest($log);
                }
            }
        }

        return $creditNoteId ?? null;
    }

    public function createCreditNote($lineItems, $contactData, $lookupContactBy, $lineAmountType = 'Exclusive')
    {
        /*
         {
  "Type": "ACCPAYCREDIT",
  "Status": "AUTHORISED",
  "Contact": {
    "ContactID": "eaa28f49-6028-4b6e-bb12-d8f6278073fc"
  },
  "Date": "2009-03-29",
  "LineAmountTypes": "Exclusive",
  "LineItems": [
    {
      "Description": "MacBook - White",
      "Quantity": 1.0000,
      "UnitAmount": 1995.00,
      "AccountCode": "720"
    }
  ]
}
         */

        $this->instantiate();

        $xeroContact = $this->getContact($contactData, $lookupContactBy);

        try {
            $log = [
                'api_name' => 'CreditNote',
                'method_name' => 'save',
                'request_data' => [
                    'Date' => date('Y-m-d'),
                    'Type' => 'ACCRECCREDIT',
                    'ContactId' => $xeroContact->getContactID(),
                    'LineAmountType' => $lineAmountType,
                    'LineItems' => $lineItems,
                    'Status' => 'AUTHORISED'
                ]
            ];

            $creditNote = (new CreditNote($this->xero))
                ->setType('ACCRECCREDIT')
                ->setStatus('AUTHORISED')
                ->setDate(new \DateTime('now'))
                ->setContact($xeroContact)
                ->setLineAmountType($lineAmountType);

            foreach ($lineItems as $lineItem) {
                $creditNote->addLineItem($lineItem);
            }

            $response = $creditNote->save();
            $creditNoteId = $creditNote->getCreditNoteID();
        } catch (\Exception $e) {
            $log['response_data'] = [$e->getMessage()];
            $creditNoteId = null;
        }
        $this->logRegest($log);

        return $creditNoteId;
    }

    public function getInvoice($invoiceId)
    {
        $this->instantiate();


        try {
            $log = [
                'api_name' => 'Invoice',
                'method_name' => 'load',
                'request_data' => [
                    'InvoiceId' => $invoiceId,
                ]
            ];
            $invoice = $this->xero->loadByGUID(Invoice::class, $invoiceId);
        } catch (\Exception $e) {
            $log['response_data'] = [$e->getMessage()];
        }
        $this->logRegest($log);


        if (isset($invoice->InvoiceID)) {
            return $invoice;
        }

        return null;
    }

    public function getInvoiceUrl($invoiceId)
    {
        try {

            $this->instantiate();

            $log = [
                'api_name' => 'Invoice',
                'method_name' => 'loadURLs',
                'request_data' => [
                    'InvoiceId' => $invoiceId,
                ]
            ];

            $invoices = $this->xero->load(Invoice::class)
                ->where('InvoiceID', $invoiceId)
                ->execute();

            foreach ($invoices as $invoice) {
                $url = new \XeroPHP\Remote\URL($this->xero, 'Invoices/' . $invoice->getGUID() . '/OnlineInvoice');
                $request = new \XeroPHP\Remote\Request($this->xero, $url);
                $request->send();
                $invoiceUrl = $request->getResponse()->getElements()[0]['OnlineInvoiceUrl'] ?? null;
                $log['response_data'] = $request->getResponse();
                break;
            }

        } catch (\Exception $e) {
            $log['response_data'] = [$e->getMessage()];
        }
        $this->logRegest($log);

        return $invoiceUrl ?? null;
    }

    protected function getDataFromContact($xeroContact)
    {
        $data = (object)[
            'firstName' => $xeroContact->getFirstName(),
            'lastName' => $xeroContact->getLastName(),
            'emailAddress' => $xeroContact->getEmailAddress(),
            'taxNumber' => $xeroContact->getTaxNumber()
        ];

        return $data;
    }

    public function createContact($details)
    {
        $this->instantiate();

        try {
            $log = [
                'api_name' => 'Contact',
                'method_name' => 'save',
                'request_data' => [
                    'Name' => $details->name,
                    'FirstName' => $details->firstName,
                    'LastName' => $details->lastName,
                    'EmailAddress' => $details->emailAddress,
                    'TaxNumber' => $details->taxNumber,
                    'Phone' => $details->phone ?? null,
                ]
            ];

            $contact = new \XeroPHP\Models\Accounting\Contact($this->xero);
            $contact->setName($details->name)->setFirstName($details->firstName)->setLastName($details->lastName)->setEmailAddress($details->emailAddress)->setTaxNumber($details->taxNumber);

            if (isset($details->phone)) {
                $phone = new Phone($this->xero);
                $phone->setPhoneType('DEFAULT');
                $phone->setPhoneNumber($details->phone);
                $contact->addPhone($phone);
            }

            $contact->save();

            if (isset($details->address)) {
                $this->addAddressToContact($contact, $details->address);
            }
        } catch (\Exception $e) {
            $log['response_data'] = [$e->getMessage()];
        }
        $this->logRegest($log);


        return $contact;
    }

    function addAddressToContact(Contact $contact, array $addressData)
    {
        $this->instantiate();

        try {
            $log = [
                'api_name' => 'Address',
                'method_name' => 'save',
                'request_data' => $addressData
            ];

            $addressType = $addressData['AddressType'] ?? 'POBOX';

            // Create new address
            $newAddress = new Address($this->xero);
            $newAddress
                ->setAddressType($addressType)
                ->setAddressLine1($addressData['AddressLine1'] ?? '')
                ->setAddressLine2($addressData['AddressLine2'] ?? '')
                ->setAddressLine3($addressData['AddressLine3'] ?? '')
                ->setCity($addressData['City'] ?? '')
                ->setRegion($addressData['Region'] ?? '')
                ->setPostalCode($addressData['PostalCode'] ?? '')
                ->setCountry($addressData['Country'] ?? '');

            $contact->addAddress($newAddress);
            // Save the updated contact
            $contact->save();
        } catch (\Exception $e) {
            $log['response_data'] = [$e->getMessage()];
        }
        $this->logRegest($log);


        return $contact;
    }

    protected function getProvider()
    {
        if (!isset($provider)) {
            static $provider;

            $provider = new Xero([
                'clientId' => $this->config->app->invoicing->xero->client_id,
                'clientSecret' => $this->config->app->invoicing->xero->client_secret,
                'redirectUri' => $this->config->app->invoicing->xero->redirect_url,
            ]);
        }

        return $provider;
    }

    /**
     * Get access token
     * @return void
     */
    protected function getAccesstoken($oauthMethod = 'client_credentials')
    {
        $tokenData = System::getVariable('xero_shortlived_client_credentials_access_token');
        if ($tokenData) {
            if (isset($tokenData->expires) && $tokenData->expires && $tokenData->expires > time()) {
                return $tokenData;
            }
        }

        $provider = $this->getProvider();
        $token = $provider->getAccessToken($oauthMethod);
        $tokenData = $this->getTokenData($token);

        if ($tokenData && isset($tokenData->token)) {
            System::setVariable('xero_shortlived_client_credentials_access_token', json_encode($tokenData));
        }

        return $tokenData;
    }

    protected function refreshToken($tokenData)
    {
        $provider = $this->getProvider();

        $newAccessToken = $provider->getAccessToken('refresh_token', [
            'refresh_token' => $tokenData['refreshToken']
        ]);

        $tokenData = $this->getTokenData($newAccessToken);
    }

    protected function getTokenData($token)
    {
        $provider = $this->getProvider();
        $tenants = $provider->getTenants($token);
        return (object)[
            'token' => $token->getToken(),
            'expires' => $token->getExpires(),
            'refreshToken' => $token->getRefreshToken(),
            'tenantId' => $tenants[0]->tenantId,
        ];
    }

    protected function instantiate()
    {
        if (!$this->xero) {
            $tokenData = $this->getAccesstoken();
            $this->xero = new XeroApp($tokenData->token, $tokenData->tenantId);
        }
    }

    protected function createInvoiceLineItem($lineInfo)
    {

        try {
            $log = [
                'api_name' => 'LineItem',
                'method_name' => 'save',
                'request_data' => [
                    'Description' => $lineInfo->description,
                    'Quantity' => $lineInfo->quantity,
                    'LineAmount' => $lineInfo->amount,
                    'AccountCode' => $lineInfo->accountCode,
                ]
            ];

            $lineItem = new \XeroPHP\Models\Accounting\Invoice\LineItem($this->xero);
            $lineItem->setDescription($lineInfo->description)
                ->setQuantity($lineInfo->quantity)
                ->setLineAmount($lineInfo->amount)
                ->setAccountCode($lineInfo->accountCode);
            if (isset($lineInfo->taxType)) {
                $lineItem->setTaxType($lineInfo->taxType);
                $log['request_data']['TaxType'] = $lineInfo->taxType;
            }
            $lineItem->save();
        } catch (\Exception $e) {
            $log['response_data'] = [$e->getMessage()];
        }

        $this->logRegest($log);

        return $lineItem;
    }

    public function getContact($contactDetails, $useField = 'name')
    {

        $this->instantiate();

        if ($useField == 'name' && !isset($contactDetails->name)) $useField = 'email';

        $log = [
            'api_name' => 'Contact',
            'method_name' => 'load',
            'request_data' => [
                'useField' => $useField,
                'contactDetails' => $contactDetails,
            ]
        ];

        if ($useField) {
            switch ($useField) {
                case 'email':
                    $contacts = $this->xero->load(Contact::class)->where('EmailAddress', $contactDetails->emailAddress)->execute();
                    break;
                case 'id':
                    $contacts = $this->xero->loadByGUID(Contact::class, $contactDetails->id);
                    break;
                default:
                    $contacts = $this->xero->load(Contact::class)->where('Name', $contactDetails->name)->execute();
                    /* leave this out for the time being in order to observe logging
                     foreach ($contacts as $con) {
                        if ($con->name == $contactDetails->name) {
                            $contact = $con;
                            break;
                        }
                    }*/

                    break;
            }
            if (!isset($contact)) {
                $contact = $contacts[0] ?? null;
            }
        }

        $log['response_data']['Contact'] = $contact;
        $log['response_data']['FoundContacts'] = $contacts;

        $this->logRegest($log);

        return $contact;
    }

    public function getContactHistory($identifier, $type)
    {

        $this->instantiate();

        switch ($type) {
            case 'email':
                $contacts = $this->xero->load(Contact::class)->where('EmailAddress', $identifier)->execute();
                break;
            case 'id':
                $contact = $this->xero->loadByGUID(Contact::class, $identifier);
                //return $this->xero->load(Contact::class)->where('ContactId', $contactDetails->emailAddress)->execute();
                break;
        }
        //$contact = $this->xero->loadByGUID(Contact::class, '699f0091-b127-4796-9f15-41a2f42abeb2');
        if ($contact ?? null) {
            $history = $contact->getHistory();
            foreach ($history as $item) {
                $this->printHistoryLineItem($item);
            }
        }

        if ($contacts ?? null) {
            foreach ($contacts as $contact) {
                echo "\n\n\n\nHISTORY FOR " . $contact->getName() . "\n\n";
                $history = $contact->getHistory();
                foreach ($history as $item) {
                    $this->printHistoryLineItem($item);
                }
            }
        }

        die("\n --- End");
    }

    protected function printHistoryLineItem($item)
    {
        echo $item->getChanges() . "\n";
        echo $item->getDateUTC()->format('Y-m-d H:i:s') . "\n";
        echo $item->getUser() . "\n";
        echo $item->getDetails() . "\n\n";
    }

}