<?php

namespace SplitPay\Integrations\FluentCart;

use SplitPay\Contracts\IntegrationInterface;
use SplitPay\Contracts\OrderAdapterInterface;
use SplitPay\Contracts\ProductAdapterInterface;
use SplitPay\Integrations\FluentCart\Adapters\FCOrderAdapter;
use SplitPay\Integrations\FluentCart\Adapters\FCProductAdapter;
use SplitPay\Integrations\FluentCart\Admin\ProductSplitPayPanel;
use SplitPay\Integrations\FluentCart\Gateways\FCStripeGateway;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class FluentCartIntegration implements IntegrationInterface {

	private FCStripeGateway $gateway;

	public function __construct() {
		$this->gateway = new FCStripeGateway();
	}

	public function getId(): string {
		return 'fluentcart';
	}

	public function getName(): string {
		return 'FluentCart';
	}

	public function getDescription(): string {
		return 'Split payments for FluentCart orders using Stripe Connect.';
	}

	public function getVersion(): string {
		return defined( 'FLUENTCART_VERSION' ) ? FLUENTCART_VERSION : '';
	}

	public function getPluginUrl(): string {
		/*
		 * 3.7.5 (PT-2): the Integrations tab "View Docs" button consumes
		 * getPluginUrl() — previously pointed at fluentcart.com, which
		 * sent customers to a third-party site instead of Split Pay's
		 * own integrations documentation on docs.splitpayplugin.com.
		 * Overridable via the `spp_integration_docs_url` filter (also
		 * receives the integration id as the second argument) so any
		 * future integration-specific subpath can replace this default.
		 */
		return (string) apply_filters(
			'spp_integration_docs_url',
			'https://docs.splitpayplugin.com/features/integrations-and-compatibility',
			$this->getId()
		);
	}

	public function isInstalled(): bool {
		return file_exists( WP_PLUGIN_DIR . '/fluent-cart/fluent-cart.php' );
	}

	public function isActive(): bool {
		return defined( 'FLUENTCART_VERSION' ) && $this->gateway->isActive();
	}

	public function getStatusDetails(): array {
		$gatewayStatus = $this->gateway->getStatusDetails();

		return [
			'integration_id'   => $this->getId(),
			'integration_name' => $this->getName(),
			'installed'        => $this->isInstalled(),
			'active'           => $this->isActive(),
			'version'          => $this->getVersion(),
			'gateway'          => $gatewayStatus,
			'configure_url'    => admin_url( 'admin.php?page=fluent-cart#/settings/payments/stripe' ),
		];
	}

	public function getOrderAdapter( $orderData ): OrderAdapterInterface {
		$order       = $orderData['order'] ?? null;
		$transaction = $orderData['transaction'] ?? null;

		if ( $order === null || $transaction === null ) {
			throw new \InvalidArgumentException(
				'FluentCart order adapter requires both "order" and "transaction" keys in $orderData.'
			);
		}

		return new FCOrderAdapter( $order, $transaction );
	}

	public function getProductAdapter(): ProductAdapterInterface {
		return new FCProductAdapter();
	}

	/** @return \SplitPay\Contracts\GatewayAdapterInterface[] */
	public function getGatewayAdapters(): array {
		return [ $this->gateway ];
	}

	public function registerHooks(): void {
		if ( class_exists( 'FluentCart\App\Modules\Integrations\BaseIntegrationManager' ) ) {
			$integrationManager = new SplitPayFCIntegrationManager();
			$integrationManager->register();
		}

		add_action( 'fluent_cart/order_paid_done', [ $this, 'handleOrderPaid' ], 10, 1 );
		add_action( 'fluent_cart/payments/stripe/webhook_charge_refunded', [ $this, 'handleRefund' ], 10, 1 );
		add_filter( 'fluent_cart/payments/stripe_metadata_onetime', [ $this, 'addSplitPayMetadata' ], 10, 2 );
	}

	public function handleOrderPaid( $eventData ): void {
		if ( ! is_array( $eventData ) ) {
			return;
		}

		$order       = $eventData['order'] ?? null;
		$transaction = $eventData['transaction'] ?? null;
		$customer    = $eventData['customer'] ?? null;

		if ( $order === null || $transaction === null ) {
			return;
		}

		$paymentMethod = $transaction->payment_method ?? ( $order->payment_method ?? '' );

		if ( $paymentMethod !== 'stripe' ) {
			return;
		}

		try {
			$orderAdapter = new FCOrderAdapter( $order, $transaction );

			// PT-fix-3.7.5 (security #6A): GET_LOCK around the entire
			// transfer batch to prevent webhook double-fire races. The WC
			// TransferEngine wraps its critical section in withOrderLock();
			// the FC path was bypassing it and relying solely on the
			// isTransferProcessed() TOCTOU check, which two simultaneous
			// fluent_cart/order_paid_done deliveries can both pass before
			// either marks the order processed. Inlined here (rather than
			// refactoring into TransferEngine::processTransfers) because
			// the FC path's body is FC-specific and routing through the
			// generic engine would require new adapter surface area.
			$lockName = 'spp_xfer_fc_' . (int) $orderAdapter->getOrderId();
			global $wpdb;
			$gotLock = (int) $wpdb->get_var( $wpdb->prepare( 'SELECT GET_LOCK(%s, 5)', $lockName ) );
			if ( $gotLock !== 1 ) {
				// Another concurrent invocation owns the lock — let it finish.
				$orderAdapter->addOrderNote( 'Split Pay: concurrent transfer batch already in progress; skipping this invocation.' );
				return;
			}

			if ( $orderAdapter->isTransferProcessed() ) {
				$wpdb->get_var( $wpdb->prepare( 'SELECT RELEASE_LOCK(%s)', $lockName ) );
				return;
			}

			$chargeId = $this->gateway->resolveChargeId( $transaction );

			if ( empty( $chargeId ) ) {
				$orderAdapter->addOrderNote( 'Split Pay: Could not resolve Stripe charge ID from transaction.' );
				return;
			}

			$secretKey = $this->gateway->getTransferSecretKey();

			if ( empty( $secretKey ) ) {
				$orderAdapter->addOrderNote( 'Split Pay: Stripe platform key not configured. Set API keys in the Integrations tab.' );
				return;
			}

			do_action( 'spp_before_process_transfers', $orderAdapter, 'fluentcart', $chargeId );

			$items = $orderAdapter->getItems();

			if ( empty( $items ) ) {
				return;
			}

			$currency       = $orderAdapter->getCurrency();
			$stripeService  = new \SplitPay\Core\StripeService( $secretKey );
			$logger         = new \SplitPay\Core\TransferLogger();
			$connectedAccts = new \SplitPay\Core\ConnectedAccounts();
			$hasTransfers   = false;
			$stripeMode     = $this->gateway->isTestMode() ? 'test' : 'live';
			$sendingMeta    = get_option( 'sending_meta', '0' ) === '1';
			$orderId        = $orderAdapter->getOrderId();
			$customerInfo   = $orderAdapter->getCustomerInfo();

			// 3.7.5 (sc-YA8): bump the per-order Stripe-idempotency attempt
			// counter at the top of each fresh batch — same fix as the WC
			// TransferEngine and the legacy transfer_controller. See the
			// docblock on spp_bump_idem_attempt() in the main file.
			$idem_attempt = \BSD_Split_Pay_Stripe_Connect_Woo\spp_bump_idem_attempt( $orderId );

			$globalSubtotal = 0.0;
			foreach ( $items as $item ) {
				if ( $item->transfer_level === 'global' ) {
					$globalSubtotal += $item->line_total;
				}
			}

			$globalAccounts = $connectedAccts->getGlobalAccounts();

			if ( $globalSubtotal > 0 && ! empty( $globalAccounts ) ) {
				$transferIndex = 0;
				foreach ( $globalAccounts as $ga ) {
					$accountId = $ga['bsd_connected_account_id'] ?? '';
					$gaType    = $ga['bsd_spscwt_type'] ?? 'percentage';
					$gaValue   = (float) ( $ga['bsd_spscwt_percentage_amount'] ?? 0 );

					if ( empty( $accountId ) || $gaValue <= 0 ) {
						continue;
					}

					$transferTotal = ( $gaType === 'percentage' )
						? $globalSubtotal * ( $gaValue / 100 )
						: min( $gaValue, $globalSubtotal );

					if ( $transferTotal <= 0 ) {
						continue;
					}

					$this->executeTransfer(
						$stripeService, $logger, $orderAdapter,
						$transferTotal, $currency, $chargeId, $accountId,
						sprintf( 'FC Order #%d - Global Transfer', $orderId ),
						$orderId, $stripeMode, $sendingMeta, $customerInfo,
						$gaType === 'percentage' ? 1 : 2,
						'spp_fc_' . $orderId . '_global_' . $transferIndex . '_' . $idem_attempt,
						$hasTransfers,
						$gaValue, $globalSubtotal
					);

					$shippingType  = $ga['bsd_global_shipping_type'] ?? '';
					$shippingValue = (float) ( $ga['bsd_global_shipping_percentage_amount'] ?? 0 );
					$shippingTotal = $orderAdapter->getShippingTotal();

					if ( $shippingTotal > 0 && ! empty( $shippingType ) && $shippingValue > 0 ) {
						$shipTransfer = ( $shippingType === 'percentage' )
							? $shippingTotal * ( $shippingValue / 100 )
							: min( $shippingValue, $shippingTotal );

						if ( $shipTransfer > 0 ) {
							$this->executeTransfer(
								$stripeService, $logger, $orderAdapter,
								$shipTransfer, $currency, $chargeId, $accountId,
								sprintf( 'FC Order #%d - Global Shipping', $orderId ),
								$orderId, $stripeMode, $sendingMeta, $customerInfo,
								$shippingType === 'percentage' ? 3 : 4,
								'spp_fc_' . $orderId . '_gship_' . $transferIndex . '_' . $idem_attempt,
								$hasTransfers
							);
						}
					}

					$transferIndex++;
				}
			}

			foreach ( $items as $item ) {
				if ( $item->transfer_level !== 'product' || empty( $item->connected_accounts ) ) {
					continue;
				}

				foreach ( $item->connected_accounts as $idx => $accountId ) {
					if ( empty( $accountId ) ) {
						continue;
					}

					$type       = $item->transfer_types[ $idx ] ?? '';
					$percentage = (float) ( $item->transfer_percentages[ $idx ] ?? 0 );
					$fixedAmt   = (float) ( $item->transfer_amounts[ $idx ] ?? 0 );

					$transferTotal = 0.0;
					if ( $type === 'percentage' && $percentage > 0 ) {
						$transferTotal = $item->line_total * ( $percentage / 100 );
					} elseif ( $type === 'amount' && $fixedAmt > 0 ) {
						$transferTotal = $fixedAmt * $item->quantity;
					}

					$transferTotal = (float) apply_filters(
						'spp_calculated_transfer_amount', $transferTotal, $item, $idx, 'fluentcart'
					);

					if ( $transferTotal <= 0 ) {
						continue;
					}

					$enteredVal = ( $type === 'percentage' ) ? $percentage : $fixedAmt;

					$this->executeTransfer(
						$stripeService, $logger, $orderAdapter,
						$transferTotal, $currency, $chargeId, $accountId,
						sprintf( 'FC Order #%d - Product #%d', $orderId, $item->product_id ),
						$orderId, $stripeMode, $sendingMeta, $customerInfo,
						$type === 'percentage' ? 8 : 9,
						'spp_fc_' . $orderId . '_' . $item->product_id . '_' . $idx . '_' . $idem_attempt,
						$hasTransfers,
						$enteredVal, $item->line_total
					);

					$shipSettings = $item->shipping_transfer_settings;
					$shipType     = $shipSettings['types'][ $idx ] ?? 'none';
					$shipPct      = (float) ( $shipSettings['percentages'][ $idx ] ?? 0 );
					$shipAmt      = (float) ( $shipSettings['amounts'][ $idx ] ?? 0 );
					$shippingTotal = $orderAdapter->getShippingTotal();

					if ( $shippingTotal > 0 && $shipType !== 'none' ) {
						$shipTransfer = ( $shipType === 'percentage' && $shipPct > 0 )
							? $shippingTotal * ( $shipPct / 100 )
							: ( ( $shipType === 'amount' && $shipAmt > 0 ) ? min( $shipAmt, $shippingTotal ) : 0 );

						if ( $shipTransfer > 0 ) {
							$this->executeTransfer(
								$stripeService, $logger, $orderAdapter,
								$shipTransfer, $currency, $chargeId, $accountId,
								sprintf( 'FC Order #%d - Product #%d Shipping', $orderId, $item->product_id ),
								$orderId, $stripeMode, $sendingMeta, $customerInfo,
								$shipType === 'percentage' ? 6 : 7,
								'spp_fc_' . $orderId . '_' . $item->product_id . '_ship_' . $idx . '_' . $idem_attempt,
								$hasTransfers
							);
						}
					}
				}
			}

			if ( $hasTransfers ) {
				$orderAdapter->markTransferProcessed();
			}

			do_action( 'spp_after_process_transfers', $orderAdapter, 'fluentcart', $chargeId );
		} catch ( \Throwable $e ) {
			if ( isset( $orderAdapter ) ) {
				$orderAdapter->addOrderNote( 'Split Pay: Unexpected error — ' . $e->getMessage() );
			}
		} finally {
			// PT-fix-3.7.5 (security #6A): always release the GET_LOCK,
			// even on exception. Lock name re-derived from order in case
			// $orderAdapter wasn't assigned before throw.
			if ( isset( $lockName ) ) {
				global $wpdb;
				$wpdb->get_var( $wpdb->prepare( 'SELECT RELEASE_LOCK(%s)', $lockName ) );
			}
		}
	}

	private function executeTransfer(
		$stripeService, $logger, $orderAdapter,
		float $amount, string $currency, string $chargeId, string $accountId,
		string $description, int $orderId, string $stripeMode,
		bool $sendingMeta, array $customerInfo,
		int $transferType, string $idempotencyKey,
		bool &$hasTransfers,
		float $enteredValue = 0, float $itemTotal = 0, float $taxAmount = 0, string $taxType = ''
	): void {
		$stripeAmount = \SplitPay\Core\CurrencyHelper::toStripeAmount( $amount, $currency );

		if ( $stripeAmount <= 0 ) {
			return;
		}

		$metadata = [
			'spp_integration' => 'fluentcart',
			'spp_order_id'    => $orderId,
		];

		if ( $sendingMeta ) {
			$metadata['Site Name']      = get_bloginfo( 'name' );
			$metadata['Site Url']       = get_bloginfo( 'url' );
			$metadata['Order Id']       = $orderId;
			$metadata['Customer Name']  = trim( ( $customerInfo['first_name'] ?? '' ) . ' ' . ( $customerInfo['last_name'] ?? '' ) );
			$metadata['Customer Email'] = $customerInfo['email'] ?? '';
		}

		$metadata = apply_filters( 'spp_transfer_metadata', $metadata, null, 'fluentcart' );

		$transferTaxValue = $taxAmount;
		$itemTransferAmt  = $amount - $transferTaxValue;

		try {
			$transfer = $stripeService->createTransfer(
				$stripeAmount, $currency, $chargeId, $accountId,
				$description, $metadata, $idempotencyKey
			);

			$logger->logTransfer( [
				'integration_source'    => 'fluentcart',
				'source_order_id'       => (string) $orderId,
				'wc_order_id'           => $orderId,
				'charge_id'             => $chargeId,
				'charge_date'           => current_time( 'mysql' ),
				'charge_description'    => $description,
				'transfer_id'           => $transfer->id,
				'transfer_destination'  => $accountId,
				'transfer_amount'       => $amount,
				'charge_amount'         => $orderAdapter->getSubtotal(),
				'stripe_mode'           => $stripeMode,
				'transfer_type'         => $transferType,
				'transfer_entered_value' => $enteredValue > 0 ? $enteredValue : $amount,
				'item_total'            => $itemTotal > 0 ? $itemTotal : $orderAdapter->getSubtotal(),
				'item_transfer_amount'  => $itemTransferAmt,
				'item_tax_total'        => $taxAmount,
				'tax_transfer_type'     => $taxType,
				'transfer_tax_value'    => $transferTaxValue,
			] );

			$orderAdapter->addOrderNote(
				sprintf(
					'Split Pay: Transferred %s %s to %s (Transfer: %s)',
					number_format( $amount, 2 ),
					strtoupper( $currency ),
					$accountId,
					$transfer->id
				)
			);

			$hasTransfers = true;
		} catch ( \Throwable $e ) {
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { error_log( '[Split Pay FC] Transfer failed for ' . $accountId . ': ' . $e->getMessage() ); } // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
			$orderAdapter->addOrderNote(
				sprintf( 'Split Pay: Transfer failed for %s — %s', $accountId, $e->getMessage() )
			);
		}
	}

	public function handleRefund( $data ): void {
		if ( ! is_array( $data ) ) {
			return;
		}

		$order       = $data['order'] ?? null;
		$transaction = $data['transaction'] ?? null;

		if ( $order === null || $transaction === null ) {
			return;
		}

		try {
			$orderAdapter = new FCOrderAdapter( $order, $transaction );
			$logger       = new \SplitPay\Core\TransferLogger();
			$transfers    = $logger->getTransfersByOrderId( $orderAdapter->getOrderId(), 'fluentcart' );

			if ( empty( $transfers ) ) {
				return;
			}

			$secretKey = $this->gateway->getTransferSecretKey();

			if ( empty( $secretKey ) ) {
				$orderAdapter->addOrderNote( 'Split Pay: Cannot reverse transfers — platform key unavailable.' );
				return;
			}

			$stripeService = new \SplitPay\Core\StripeService( $secretKey );

			foreach ( $transfers as $transfer ) {
				// 3.7.6 fix: the bsd_scsp_transfer_log table has NO `status` column (only successful
				// transfers are ever logged — see executeTransfer), so the old `status === 'success'`
				// guard ALWAYS failed and FluentCart refunds never reversed the vendor transfer. Match
				// the WooCommerce handleRefund(): reverse any logged row whose transfer_id is a real tr_.
				if ( empty( $transfer['transfer_id'] ) || strpos( (string) $transfer['transfer_id'], 'tr_' ) !== 0 ) {
					continue;
				}

				try {
					// 3.7.5 (sc-DUP-REFUND): deterministic idempotency key so a
					// duplicate refund webhook can't double-reverse the vendor. Same
					// shape as the WooCommerce path: spp_rev_<order>_<transferId>_<cents>.
					$fc_rev_currency = strtolower( (string) $orderAdapter->getCurrency() ) ?: 'usd';
					$fc_rev_cents    = \SplitPay\Core\CurrencyHelper::toStripeAmount( (float) ( $transfer['transfer_amount'] ?? 0 ), $fc_rev_currency );
					$fc_rev_key      = 'spp_rev_' . $orderAdapter->getOrderId() . '_' . $transfer['transfer_id'] . '_' . $fc_rev_cents;
					$stripeService->createTransferReversal( $transfer['transfer_id'], null, $fc_rev_key );

					$orderAdapter->addOrderNote(
						sprintf( 'Split Pay: Reversed transfer %s', $transfer['transfer_id'] )
					);
				} catch ( \Throwable $e ) {
					$orderAdapter->addOrderNote(
						sprintf(
							'Split Pay: Failed to reverse transfer %s — %s',
							$transfer['transfer_id'],
							$e->getMessage()
						)
					);
				}
			}
		} catch ( \Throwable $e ) {
			// Silently fail — refund webhook should not fatally break.
		}
	}

	/**
	 * @param array $metadata
	 * @param mixed $context
	 * @return array
	 */
	public function addSplitPayMetadata( $metadata, $context ): array {
		if ( ! is_array( $metadata ) ) {
			$metadata = [];
		}

		$metadata['spp_integration'] = 'fluentcart';

		return $metadata;
	}

	public function registerAdminUI(): void {
		$panel = new ProductSplitPayPanel();
		$panel->register();
	}
}
