<?php

/**
 * Vvveb
 *
 * Copyright (C) 2022  Ziadin Givan
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 */

namespace Vvveb\Plugins\Omnipay;

use Omnipay\Omnipay;
use function Vvveb\isSecure;
use Vvveb\Sql\Region_groupSQL;
use Vvveb\System\PaymentMethod;
use Vvveb\System\Cart\Order;
use function Vvveb\url;

include __DIR__ . '/vendor/autoload.php';

if (! defined('V_VERSION')) {
	die('Invalid request!');
}

class Payment extends PaymentMethod {
	private $namespace = 'omnipay';
	
	private $gateway;

	private $method_data = [
		'name'            => 'omnipay',
		'title'           => 'Omnipay',
		'description'     => 'Omnipay',
		'text'            => '',
		'region_id'       => 0,
		'cost'            => 0,
		'tax_type_id'     => 0,
		'region_group_id' => 0,
		'payment_status_id' => 1,
		'pending_payment_status_id' => 2,
		'success_payment_status_id' => 4,
		'error_payment_status_id' => 5,
	];

	public function __construct(object $cart, array $options = []) {
		$this->cart        = $cart;
		$this->method_data = $options + $this->method_data;
	}
	
	public function getMethod(array &$checkoutInfo = [], array $options = []): array|false {
		$this->method_data         = $options + $this->method_data;
		$this->method_data['cost'] = floatval($this->method_data['cost']);
		//$this->getCost();

		//if set only for specific region(s) check if address matches region
		$region_group_id     = $this->method_data['region_group_id'];
		$shipping_country_id = $checkoutInfo['shipping_country_id'] ?? false;
		$shipping_region_id  = $checkoutInfo['shipping_region_id'] ?? 0;

		if ($region_group_id != 0) {
			$region  = new Region_groupSQL();
			$regions = [];
			$params  = ['region_group_id' => (int)$region_group_id, 'country_id' => (int)$shipping_country_id, 'region_id' => (int)$shipping_region_id];
			$regions = $region->isRegion($params);

			if (isset($regions['count']) && $regions['count'] == 0) {
				return [];
			}
		}

		$gateway = $this->method_data['gateway'] ?? '';
		$template = DIR_PUBLIC . "plugins/{$this->namespace}/{$gateway}-form.html";

		if (APP == 'app' && file_exists($template)) {
			$form                        = file_get_contents($template);
			$this->method_data['render'] = $form;
		}

		return $this->method_data;
	}

	public function init(): void {
		//remove previous total and tax
		$this->cart->removeTotal($this->namespace);
		$this->cart->removeTax($this->namespace);
	}

	public function setMethod(): void {
		if ($this->method_data['cost'] || $this->method_data['text']) {
			if ($this->method_data['tax_type_id']) {
				$this->cart->addTax($this->namespace, $this->method_data['cost'], $this->method_data['tax_type_id']);
			}

			$this->cart->addTotal('payment', $this->namespace, $this->method_data['title'] , $this->method_data['cost'], $this->method_data['text']);
		}
	}

	/**
	 * Initialize the Omnipay gateway from method data.
	 *
	 * @param string $gateway Gateway name.
	 *
	 * @return bool True if the gateway was created and initialized.
	 */
	private function initGateway(string $gateway): bool {

		try {
			$this->gateway = Omnipay::create($gateway);
			$parameters    = [];

			foreach ($this->gateway->getDefaultParameters() as $param => $value) {
				$parameters[$param] = $this->method_data[$param] ?? $value;
			}

			$this->gateway->setApiKey($parameters['apiKey'] ?? '');
			$this->gateway->initialize($parameters);

			return true;
		} catch (\Exception $e) {
			error_log('Omnipay gateway init failed: ' . $e->getMessage());

			return false;
		}
	}

	/**
	 * Get order payment data (transaction reference, errors) from the order.
	 *
	 * @param int|string $order_id Order id.
	 *
	 * @return array<string, mixed> Payment data array.
	 */
	private function getPaymentData(int|string $order_id): array {
		$order       = Order::getInstance();
		$orderData   = $order->get($order_id)['order'] ?? [];
		$paymentData = json_decode($orderData['payment_data'] ?? '[]', true);

		return is_array($paymentData) ? $paymentData : [];
	}

	/**
	 * Send an Omnipay request and update the order status.
	 *
	 * @param int|string           $order_id      Order id.
	 * @param object               $request       Omnipay request to send.
	 * @param int|string           $successStatus Payment status id on success.
	 * @param int|string           $errorStatus   Payment status id on error.
	 * @param array<string, mixed> $paymentData   Payment data to store.
	 *
	 * @return bool True if the request was successful.
	 */
	private function sendRequest(int|string $order_id, object $request, int|string $successStatus, int|string $errorStatus, array $paymentData = []): bool {
		$order = Order::getInstance();

		try {
			$response              = $request->send(false);
			$transactionReference  = $response->getTransactionReference();
			$transactionId		   = $response->getTransactionId();
			
			if ($transactionReference) {
				$paymentData          += ['transaction_reference' => $transactionReference];
			}			
			
			if ($transactionId) {
				$paymentData          += ['transaction_id' => $transactionId];
			}

			if ($response->isSuccessful()) {
				$order->edit(['payment_status_id' => $successStatus, 'payment_data' => json_encode($paymentData)], $order_id);

				return true;
			}

			$paymentData['error'] = $response->getMessage();
			
			$data = ['payment_data' => json_encode($paymentData)];
			if ($successStatus) {
				$data['payment_status_id'] = $successStatus;
			}

			$order->edit($data, $order_id);
			error_log($paymentData['error']);

			return false;
		} catch (\Exception $e) {
			$paymentData['error'] = $e->getMessage();
			$data = ['payment_status_id' => $errorStatus, 'payment_data' => json_encode($paymentData)];

			if ($errorStatus) {
				$data['payment_status_id'] = $errorStatus;
			}

			$order->edit($data, $order_id);
			error_log($paymentData['error']);

			return false;
		}
	}

	/**
	 * Authorize request
	 *
	 * @param array<string, mixed> &$checkoutInfo Checkout information passed by reference.
	 *
	 * @return bool True on success.
	 */
	public function authorize(array &$checkoutInfo = []): bool {
		$gateway = $this->method_data['gateway'] ?? '';

		if (! $this->initGateway($gateway)) {
			return false;
		}

		$order_id    = $checkoutInfo['order_id'] ?? '';
		$paymentData = $this->getPaymentData($order_id);
		$amount      = round(floatval($checkoutInfo['total'] ?? 0), 2);

		$options = [
			'amount'   => $amount,
			'currency' => $checkoutInfo['currency'] ?? '',
		];

		if (! empty($paymentData['transaction_reference'])) {
			$options['transactionReference'] = $paymentData['transaction_reference'];
		}		
		
		if (! empty($paymentData['transaction_id'])) {
			$options['transactionId'] = $paymentData['transaction_id'];
		}

		return $this->sendRequest($order_id, $this->gateway->authorize($options), $this->method_data['success_payment_status_id'], $this->method_data['error_payment_status_id'], $paymentData);
	}
	
	/**
	 * Purchase request
	 *
	 * @param array<string, mixed> &$checkoutInfo Checkout information passed by reference.
	 *
	 * @return bool True on success.
	 */
	public function purchase(array &$checkoutInfo = []): bool {
		$gateway = $this->method_data['gateway'] ?? '';
		$index   = $this->method_data['index'] ?? '';
		$key     = \Vvveb\getConfig('app.key');

		$urlParams  = ['host' => $checkoutInfo['site_url'] ?? false, 'gateway' => $gateway, 'key' => $key, 'index' => $index, 'order_id' => $checkoutInfo['order_id'], 'scheme' => isSecure() ? 'https' : 'http'];

		$successUrl = url('plugins/omnipay/index/success', $urlParams);
		$cancelUrl  = url('plugins/omnipay/index/cancel', $urlParams);
		$notifyUrl  = url('plugins/omnipay/index/notify', $urlParams);

		if (! $this->initGateway($gateway)) {
			return false;
		}

		$order_id  = $checkoutInfo['order_id'] ?? '';
		$siteTitle = $checkoutInfo['site']['description']['title'] ?? 'Vvveb';
		$currency  = $checkoutInfo['currency'] ?? '';
		//amount must be in cents so multiply with 100
		//$amount = intval(round(floatval($checkoutInfo['total']), 2) * 100);
		$amount = round(floatval($checkoutInfo['total']), 2);
		
		$paymentData = $checkoutInfo['payment_data'];

		$options = [
			'amount'        => $amount,
			'currency'      => $currency,
			'notifyUrl'     => $notifyUrl,
			'returnUrl'     => $successUrl,
			'cancelUrl'     => $cancelUrl,
			'description'   => "Order #$order_id on $siteTitle",
			//'transactionId' => $checkoutInfo['order_id'],
		];

		if (isset($checkoutInfo['card'])) {
			$options['card'] = $checkoutInfo['card'];
		}

		$payment_data = ['gateway' => $gateway];
		$order        = Order::getInstance();

		try {
			$response             = $this->gateway->purchase($options)->send(false);
			$transactionReference = $response->getTransactionReference(); // a reference generated by the payment gateway
			$transactionId        = $response->getTransactionId();
			
			if ($transactionReference) {
				$payment_data['transaction_reference'] = $transactionReference;
			}
			
			if ($transactionId) {
				$payment_data['transaction_id'] = $transactionId;
			}

			if ($response->isRedirect()) {
				$order->edit(['payment_status_id' => $this->method_data['pending_payment_status_id'], 'payment_data' => json_encode($payment_data)], $order_id);

				// Redirect to offsite payment gateway
				$response->redirect();
error_log('redirect');
				return true;
			}

			if ($response->isSuccessful()) {
				$order->edit(['payment_status_id' => $this->method_data['success_payment_status_id'], 'payment_data' => json_encode($payment_data)], $order_id);
error_log('success');
				return true;
			}

			// Payment failed
			$payment_data['error'] = $response->getMessage();
			$order->edit(['payment_status_id' => $this->method_data['error_payment_status_id'], 'payment_data' => json_encode($payment_data)], $order_id);
			error_log('payment failed: ' . $payment_data['error']);

			return false;

		} catch (\Exception $e) {
			// Payment failed
			$payment_data['error'] = $e->getMessage();
			$order->edit(['payment_status_id' => $this->method_data['error_payment_status_id'], 'payment_data' => json_encode($payment_data)], $order_id);
			error_log('exception: ' . $payment_data['error']);

			return false;
		}
	}
	
	/**
	 * Complete purchase request
	 *
	 * @param array<string, mixed> &$checkoutInfo Checkout information passed by reference.
	 *
	 * @return bool True on success.
	 */
	public function completePurchase(array &$checkoutInfo = []): bool {	
	}	

	/**
	 * Complete an authorize request, called when the customer returns from the payment gateway.
	 *
	 * @param array<string, mixed> &$checkoutInfo Checkout information passed by reference.
	 *
	 * @return bool True on success.
	 */
	public function completeAuthorize(array &$checkoutInfo = []): bool {
		$gateway = $this->method_data['gateway'] ?? '';

		if (! $this->initGateway($gateway)) {
			return false;
		}

		$order_id    = $checkoutInfo['order_id'] ?? '';
		$paymentData = $this->getPaymentData($order_id);
		$amount      = round(floatval($checkoutInfo['total'] ?? 0), 2);

		$options = [
			'amount'   => $amount,
			'currency' => $checkoutInfo['currency'] ?? '',
		];

		if (! empty($paymentData['transaction_reference'])) {
			$options['transactionReference'] = $paymentData['transaction_reference'];
		}		
		
		if (! empty($paymentData['transaction_id'])) {
			$options['transactionId'] = $paymentData['transaction_id'];
		}

		return $this->sendRequest($order_id, $this->gateway->completeAuthorize($options), $this->method_data['success_payment_status_id'], $this->method_data['error_payment_status_id'], $paymentData);
	}

	/**
	 * Capture a previously authorized payment.
	 *
	 * @param array<string, mixed> &$checkoutInfo Checkout information passed by reference.
	 *
	 * @return bool True on success.
	 */
	public function capture(array &$checkoutInfo = []): bool {
		$gateway = $this->method_data['gateway'] ?? '';

		if (! $this->initGateway($gateway)) {
			return false;
		}

		if (method_exists($this->gateway, 'supportsCapture') && ! $this->gateway->supportsCapture()) {
			return false;
		}

		$order_id    = $checkoutInfo['order_id'] ?? '';
		$paymentData = $this->getPaymentData($order_id);

		if (empty($paymentData['transaction_reference'])) {
			return false;
		}

		$options = [
			'amount'               => round(floatval($checkoutInfo['total'] ?? 0), 2),
			'currency'             => $checkoutInfo['currency'] ?? '',
			'transactionReference' => $paymentData['transaction_reference'],
		];

		if (isset($paymentData['transaction_id']) && $paymentData['transaction_id']) {
			$options['transactionId'] = $paymentData['transaction_id'];
		}	

		return $this->sendRequest($order_id, $this->gateway->capture($options), 3, $this->method_data['error_payment_status_id'], $paymentData);
	}

	/**
	 * Refund a captured payment.
	 *
	 * @param array<string, mixed> &$checkoutInfo Checkout information passed by reference.
	 *
	 * @return bool True on success.
	 */
	public function refund(array &$checkoutInfo = []): bool {
		$paymentData = $checkoutInfo['payment_data'] ?? [];
		$order_id    = $checkoutInfo['order_id'] ?? '';
		if (!$paymentData && $order_id) {
			$paymentData = $this->getPaymentData($order_id);
		}
		//gateway":"Mollie","transaction_reference":"tr_3twoj2qhZWxyYUwgRVPVJ","payment_method":"Mollie"}
		$gateway = $paymentData['gateway'] ?? '';
		if (! $this->initGateway($gateway)) {
			return false;
		}

		if (method_exists($this->gateway, 'supportsRefund') && ! $this->gateway->supportsRefund()) {
			return false;
		}

		if (empty($paymentData['transaction_reference'])) {
			return false;
		}

		$options = [
			'amount'               => round(floatval($checkoutInfo['total'] ?? 0), 2),
			'currency'             => $checkoutInfo['currency'] ?? '',
			'transactionReference' => $paymentData['transaction_reference'],
		];

		if (isset($paymentData['transaction_id']) && $paymentData['transaction_id']) {
			$options['transactionId'] = $paymentData['transaction_id'];
		}		

		return $this->sendRequest($order_id, $this->gateway->refund($options), 6, $this->method_data['error_payment_status_id'], $paymentData);
	}

	/**
	 * Void an authorized payment.
	 *
	 * @param array<string, mixed> &$checkoutInfo Checkout information passed by reference.
	 *
	 * @return bool True on success.
	 */
	public function void(array &$checkoutInfo = []): bool {
		$paymentData = $checkoutInfo['payment_data'] ?? [];
		$order_id    = $checkoutInfo['order_id'] ?? '';
		$gateway     = $this->method_data['gateway'] ?? '';
		
		if (!$paymentData && $order_id) {
			$paymentData = $this->getPaymentData($order_id);
		}
		
		$gateway = $paymentData['gateway'] ?? $this->method_data['gateway'] ?? '';
		if (! $this->initGateway($gateway)) {
			return false;
		}

		if (method_exists($this->gateway, 'supportsVoid') && ! $this->gateway->supportsVoid()) {
			return false;
		}


		if (empty($paymentData['transaction_reference'])) {
			return false;
		}

		$options = [
			'amount'               => round(floatval($checkoutInfo['total'] ?? 0), 2),
			'currency'             => $checkoutInfo['currency'] ?? '',
			'transactionReference' => $paymentData['transaction_reference'],
		];
		
		if (isset($paymentData['transaction_id']) && $paymentData['transaction_id']) {
			$options['transactionId'] = $paymentData['transaction_id'];
		}

		return $this->sendRequest($order_id, $this->gateway->void($options), 5, $this->method_data['error_payment_status_id'],$this->method_data['error_payment_status_id'], $paymentData);
	}
}
