<?php

namespace App\Http\Controllers;

use Carbon\Carbon;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use PayPalCheckoutSdk\Core\PayPalHttpClient;
use PayPalCheckoutSdk\Core\ProductionEnvironment;
use PayPalCheckoutSdk\Core\SandboxEnvironment;
use PayPalCheckoutSdk\Orders\OrdersCreateRequest;
use PayPalCheckoutSdk\Orders\OrdersCaptureRequest;
use PayPalHttp\HttpException;

use App\Models\{Checkout,Invoice,Payment,PaymentItem};

class PaypalController extends Controller
{
	private PayPalHttpClient $client;

	protected const cart_url = 'u/checkout/cart';

	// Create a new instance with our paypal credentials
	public function __construct()
	{
		if (config('paypal.settings.mode') == 'sandbox')
			$environment = new SandboxEnvironment(config('paypal.sandbox_client_id'),config('paypal.sandbox_secret'));
		else
			$environment = new ProductionEnvironment(config('paypal.live_client_id'),config('paypal.live_secret'));

		$this->client = new PayPalHttpClient($environment);
	}

	public function cancel()
	{
		return redirect(self::cart_url);
	}

	/**
	 * Authorize a paypal payment, and redirect the user to pay.
	 *
	 * @return RedirectResponse
	 * @throws \PayPalHttp\IOException
	 */
	public function authorise()
	{
		$co = Checkout::where('name','ilike','paypal')->firstOrFail();

		$currency = 'AUD'; // @todo TO determine from DB.;
		$cart = request()->session()->get('invoice.cart');

		if (! $cart)
			return redirect('home');

		$invoices = Invoice::find($cart);

		$paypal = new OrdersCreateRequest();
		$paypal->prefer('return=minimal');

		// Paypal Purchase Units
		$items = collect();
		foreach ($invoices as $io) {
			$fee = $co->fee($io->due,count($cart));
			$total = round($io->due+$fee,2);

			$items->push([
				'reference_id'=>$io->id,
				'invoice_id'=>$io->id,
				'description'=>'Invoice Payment',
				'custom_id'=>sprintf('%s:%s',$io->account_id,$fee*100),
				'amount'=>[
					'value'=>$total,
					'currency_code'=>$currency,
					'breakdown'=>[
						'item_total'=>[
						'value'=>$total,
						'currency_code'=>$currency,
						]
					],
				],
				'items'=>[
					[
						'name'=>'Invoice: '.$io->id,
						'unit_amount'=>[
							'value'=>$total,
							'currency_code'=>$currency,
						],
						'quantity'=>1,
						'description'=>'Invoice Payment',
						'category'=>'DIGITAL_GOODS',
					],
				]
			]);
		}

		$data = collect();
		$data->put('intent','CAPTURE');
		$data->put('purchase_units',$items->toArray());

		$data->put('application_context',[
			'return_url' => url('pay/paypal/capture'),
			'cancel_url' => url(self::cart_url),
		]);

		$paypal->body = $data->toArray();

		try {
			$response = $this->client->execute($paypal);

		} catch (HttpException $e) {
			Log::error('Paypal Exception',['request'=>$paypal,'response'=>$e->getMessage()]);

			return redirect(self::cart_url)
				->withErrors('Paypal Exception: '.$e->getCode());

		} catch (\HttpException $e) {
			Log::error('HTTP Exception',['request'=>$this->client,'response'=>$e->getMessage()]);

			return redirect(self::cart_url)
				->withErrors('HTTP Exception: '.$e->getCode());
		}

		// Get the approval link
		$redirect_url = '';
		foreach ($response->result->links as $link) {
			if ($link->rel == 'approve') {
				$redirect_url = $link->href;
				break;
			}
		}

		if ($redirect_url)
			return redirect()
				->away($redirect_url);

		return redirect(self::cart_url)
			->withErrors('An error occurred with Paypal?');
	}

	/**
	 * Capture a paypal payment
	 *
	 * @param Request $request
	 * @return RedirectResponse
	 * @throws \PayPalHttp\IOException
	 */
	public function capture(Request $request)
	{
		$paypal = new OrdersCaptureRequest($request->query('token'));
		$paypal->prefer('return=representation');

		$redirect_url = '';

		try {
			$response = $this->client->execute($paypal);

		} catch (HttpException $e) {
			$result = json_decode($e->getMessage());

			Log::error(sprintf('Paypal Declined: Code: %s, DebugID: %s, Name: %s,Message: %s',$e->getCode(),$result->debug_id,$result->name,$result->message));

			switch ($result->name) {
				case 'UNPROCESSABLE_ENTITY':
					foreach ($result->details as $detail)
						Log::error(sprintf('Paypal Declined: Issue: %s Message: %s',$detail->issue,$detail->description));

					// If we got a redirect link, lets redirect
					foreach ($result->links as $link) {
						if ($link->rel == 'redirect') {
							$redirect_url = $link->href;
							break;
						}
					}

					break;

				default:
					Log::error(sprintf('Paypal Unhandled: %s',$result));
			}

			// If we got a redirect.
			if ($redirect_url) {
				Log::error('Paypal Capture: Redirect back to Paypal.');

				return redirect()
					->away($redirect_url);
			}

			return redirect(self::cart_url)
				->withErrors('An error occurred with Paypal?');

		} catch (\HttpException $e) {
			Log::error('HTTP Exception',['request'=>$paypal,'response'=>$e->getMessage()]);

			return redirect(self::cart_url)
				->withErrors('HTTP Exception: '.$e->getCode());
		}

		if ((! $response) || (! $response->result->purchase_units)) {
			Log::error('Paypal Capture: No Purchase Units?');

			return redirect(self::cart_url)
				->withErrors('Paypal Exception: NPU');
		}

		$co = Checkout::where('name','ilike','paypal')->firstOrFail();

		// If we got here, we got a payment
		foreach ($response->result->purchase_units as $pu) {
			foreach ($pu->payments->captures as $cap) {
				$po = new Payment;
				$po->active = TRUE;

				switch ($cap->status) {
					case 'PENDING':
						$po->pending_status = TRUE;
						$po->pending = $cap->status_details->reason;
						break;

					case 'FAILED':
						Log::error(sprintf('Paypal Payment Failed: Invoice: %s (%s).',$pu->invoice_id,$cap->error->details[0]->description));
						continue 2;

					default:
						$po->pending_status = TRUE;
						$po->pending = $cap->status_details->reason ?? 'Unknown Status';
						break;
				}

				$po->paid_at = Carbon::parse($cap->create_time);
				$po->checkout_id = $co->id;
				$po->checkout_data = $cap->id;

				list($account_id,$fee) = explode(':',$cap->custom_id);
				$po->fees_amt = $fee/100;
				$po->total_amt = $cap->amount->value-$po->fees_amt;
				$po->account_id = $account_id;

				$pio = new PaymentItem;
				$pio->site_id = 1;              // @todo To implement
				$pio->invoice_id = $cap->invoice_id;
				$pio->amount = $cap->amount->value-$po->fees_amt;

				$po->items->push($pio);

				// @todo To store payment fees on invoice

				try {
					$po->pushNew();

				} catch (\Exception $e) {
					Log::error('Error recording payment',['po'=>$po,'e'=>$e->getMessage(),'token'=>$request->query('token')]);
				}
			}
		}

		$request->session()->forget('invoice.cart');

		Log::info('Paypal Payment Recorded',['po'=>$po->id]);

		return redirect('home')
			->with('success','Payment recorded thank you.');
	}
}