<?php

namespace App\Jobs;

use Carbon\Carbon;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Log;

use App\Models\{Account,Invoice,Payment,PaymentItem,ProviderToken,Site};
use Intuit\Models\Payment as PaymentModel;

/**
 * Synchronise payments with our payments.
 *
 * This will:
 * + Create/Update the payment in our system
 * + Associate the payment to the same invoice in our system
 */
class AccountingPaymentSync implements ShouldQueue
{
	use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

	private const LOGKEY = 'JPS';

	private PaymentModel $pmi;
	private ProviderToken $to;

	/**
	 * Create a new job instance.
	 *
	 * @return void
	 */
	public function __construct(ProviderToken $to,PaymentModel $pmi)
	{
		$this->pmi = $pmi;
		$this->to = $to;
	}

	/**
	 * Execute the job.
	 *
	 * @return void
	 * @throws \Exception
	 */
	public function handle()
	{
		// @todo Can this be automatically determined?
		$site = Site::findOrFail($this->to->site_id);
		Config::set('site',$site);

		// See if we are already linked
		if (($x=$this->to->provider->payments->where('pivot.ref',$this->pmi->id))->count() === 1) {
			$o = $x->pop();

		} else {
			// Find the account
			$ao = Account::select('accounts.*')
				->join('account__provider',['account__provider.account_id'=>'accounts.id'])
				->where('provider_oauth_id',$this->to->provider_oauth_id)
				->where('ref',$this->pmi->account_ref)
				->single();

			if (! $ao) {
				Log::alert(sprintf('%s:Account not found for payment [%s:%d]',self::LOGKEY,$this->pmi->id,$this->pmi->account_ref));
				return;
			}

			// Create the payment
			$o = new Payment;
			$o->account_id = $ao->id;
			$o->site_id = $ao->site_id;	// @todo Automatically determine
		}

		// Update the payment details
		$o->paid_at = $this->pmi->date_paid;
		$o->active = TRUE;
		$o->checkout_id = 2;	// @todo
		$o->total_amt = $this->pmi->total_amt;
		$o->notes = 'Imported from Intuit';
		$o->save();

		Log::info(sprintf('%s:Recording payment [%s:%3.2f]',self::LOGKEY,$this->pmi->id,$this->pmi->total_amt));

		$o->providers()->syncWithoutDetaching([
			$this->to->provider->id => [
				'ref' => $this->pmi->id,
				'synctoken' => $this->pmi->synctoken,
				'created_at'=>Carbon::create($this->pmi->created_at),
				'updated_at'=>Carbon::create($this->pmi->updated_at),
				'site_id'=>$this->to->site_id,
			],
		]);

		// Load the invoice that this payment pays
		$invoices = collect();
		foreach ($this->pmi->lines() as $item => $amount) {
			$invoice = Invoice::select('invoices.*')
				->join('invoice__provider',['invoice__provider.invoice_id'=>'invoices.id'])
				->where('provider_oauth_id',$this->to->provider_oauth_id)
				->where('ref',$item)
				->single();

			$invoices->put($item,$invoice);
		}

		// Delete existing paid invoices that are no longer paid
		foreach ($o->items as $pio)
			if ($invoices->pluck('id')->search($pio->invoice_id) === FALSE)
				$pio->delete();

		// Update payment items
		foreach ($this->pmi->lines() as $item => $amount) {
			if (! $invoices->has($item)) {
				Log::alert(sprintf('%s:Invoice [%s] not recorded, payment not assigned',self::LOGKEY,$item));
				continue;
			}

			$io = $invoices->get($item);

			// If the payment item already exists
			if (($x=$o->items->where('invoice_id',$io->id))->count()) {
				$pio = $x->pop();

			} else {
				$pio = new PaymentItem;
				$pio->invoice_id = $io->id;
				$pio->site_id = $io->site_id;
			}

			$pio->amount = $amount;
			$o->items()->save($pio);
		}

		Log::alert(sprintf('%s:Payment updated [%s:%s]',self::LOGKEY,$o->id,$this->pmi->id));
	}
}