<?php

namespace SplitPay\Integrations\WooCommerce\Adapters;

use Automattic\WooCommerce\Utilities\OrderUtil;
use SplitPay\Contracts\OrderAdapterInterface;
use SplitPay\Contracts\TransferableItem;

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

class WCOrderAdapter implements OrderAdapterInterface {

	private \WC_Order $order;
	private string $requestBody;

	public function __construct( \WC_Order $order, string $requestBody = '' ) {
		$this->order       = $order;
		$this->requestBody = $requestBody;
	}

	public function getOrderId(): int {
		return $this->order->get_id();
	}

	/** @return TransferableItem[] */
	public function getItems(): array {
		$items = [];

		foreach ( $this->order->get_items() as $item ) {
			if ( function_exists( 'wc_pb_is_bundled_order_item' ) && wc_pb_is_bundled_order_item( $item ) ) {
				continue;
			}

			$product = $item->get_product();
			if ( ! $product ) {
				continue;
			}

			$product_id = $item->get_product_id();
			$meta_id    = $product_id;

			if ( $product->is_type( 'variation' ) || $product->is_type( 'subscription_variation' ) ) {
				$meta_id = $product->get_id();
			}

			$quantity = $item->get_quantity();

			// 3.7.3 fix (Carlos Lozano / sc-7170): use the order LINE ITEM subtotal as
			// the source of truth for product price, not $product->get_price(). The
			// product's get_price() returns the BASE / default price configured on the
			// product (e.g. $1 for a "Name Your Price" / WPC NYP product), not what
			// the customer actually paid. Customer-chosen-price plugins (WPC Name Your
			// Price, WooCommerce Name Your Price, Subscriptions with custom prices,
			// dynamic-pricing rules, etc.) all capture the final price in the line-
			// item subtotal. Reading $product->get_price() caused percentage transfers
			// to be computed against the $1 base, so 80% of a $100 chosen-price order
			// transferred only $0.80 instead of $80. Symptom reported by Carlos Lozano
			// on May 19 2026 for his WPC Name Your Price marketplace setup.
			$line_subtotal = (float) $item->get_subtotal();
			$product_price = $quantity > 0 ? $line_subtotal / $quantity : (float) $product->get_price();

			$wapf_meta = $item->get_meta( '_wapf_meta', true );
			if ( ! empty( $wapf_meta ) && ! empty( $wapf_meta['settings'] ) ) {
				$formatted_meta = $item->get_all_formatted_meta_data();
				foreach ( $formatted_meta as $formatted ) {
					if ( isset( $wapf_meta['settings'][ $formatted->key ][0]['field'] ) ) {
						$field_key = $wapf_meta['settings'][ $formatted->key ][0]['field'];
						if ( isset( $wapf_meta['fields'][ $field_key ]['values'][0]['calc_price'] ) ) {
							// APFW custom field add-on already part of the line subtotal
							// for current WC versions, but keep the legacy add-back for
							// older configurations where the addon stores extras separately.
							$apfw_extra      = (float) $wapf_meta['fields'][ $field_key ]['values'][0]['calc_price'];
							$line_from_addon = ( $product_price + $apfw_extra ) * $quantity;
							if ( $line_from_addon > $line_subtotal ) {
								$product_price = $product_price + $apfw_extra;
							}
						}
					}
				}
			}

			$connected_accounts   = get_post_meta( $meta_id, '_bsd_spscwt_product_connected_account', true ) ?: [];
			$transfer_types       = get_post_meta( $meta_id, '_bsd_spscwt_product_type', true ) ?: [];
			$transfer_percentages = get_post_meta( $meta_id, '_stripe_connect_split_pay_transfer_percentage', true ) ?: [];
			$transfer_amounts     = get_post_meta( $meta_id, '_bsd_spscwt_product_amount', true ) ?: [];

			$shipping_types       = get_post_meta( $meta_id, '_bsd_spscwt_shipping_type', true ) ?: [];
			$shipping_percentages = get_post_meta( $meta_id, '_bsd_prod_shipping_percentage', true ) ?: [];
			$shipping_amounts     = get_post_meta( $meta_id, '_bsd_spscwt_shipping_amount', true ) ?: [];

			$transfer_level = empty( $connected_accounts ) ? 'global' : 'product';

			$get_taxes    = $item->get_taxes();
			$tax_subtotal = ( is_array( $get_taxes ) && isset( $get_taxes['subtotal'] ) && is_array( $get_taxes['subtotal'] ) )
				? $get_taxes['subtotal']
				: [];

			// Prefer the line subtotal exactly so coupons applied by WC don't double-up.
			$line_total       = $line_subtotal > 0 ? $line_subtotal : $product_price * $quantity;
			$product_discount = 0.0;

			$items[] = new TransferableItem(
				$meta_id,
				$product->get_type(),
				$quantity,
				$product_price,
				$line_total,
				$product_discount,
				$tax_subtotal,
				0.0,
				is_array( $connected_accounts ) ? $connected_accounts : [],
				is_array( $transfer_types ) ? $transfer_types : [],
				is_array( $transfer_percentages ) ? $transfer_percentages : [],
				is_array( $transfer_amounts ) ? $transfer_amounts : [],
				[
					'types'       => is_array( $shipping_types ) ? $shipping_types : [],
					'percentages' => is_array( $shipping_percentages ) ? $shipping_percentages : [],
					'amounts'     => is_array( $shipping_amounts ) ? $shipping_amounts : [],
				],
				$transfer_level
			);
		}

		return $items;
	}

	public function getSubtotal(): float {
		return (float) $this->order->get_subtotal();
	}

	public function getShippingTotal(): float {
		return (float) $this->order->get_shipping_total();
	}

	public function getShippingMethods(): array {
		$methods = [];
		foreach ( $this->order->get_shipping_methods() as $method ) {
			$methods[] = [
				'method_id'    => $method->get_method_id(),
				'method_title' => $method->get_method_title(),
				'total'        => (float) $method->get_total(),
			];
		}
		return $methods;
	}

	public function getTaxItems(): array {
		$taxes = [];
		foreach ( $this->order->get_items( 'tax' ) as $tx_item ) {
			$rate_id             = $tx_item->get_rate_id();
			$taxes[ $rate_id ] = [
				'tax_total'      => (float) $tx_item->get_tax_total(),
				'tax_ship_total' => (float) $tx_item->get_shipping_tax_total(),
			];
		}
		return $taxes;
	}

	public function getCoupons(): array {
		$coupons = [];
		foreach ( $this->order->get_coupons() as $coupon ) {
			$code             = $coupon->get_code();
			$original         = new \WC_Coupon( $code );
			$coupons[]        = [
				'code'            => $code,
				'discount_type'   => $original->get_discount_type(),
				'discount'        => (float) $coupon->get_discount(),
				'discount_amount' => (float) $original->get_amount(),
				'product_ids'     => $original->get_product_ids(),
			];
		}
		return $coupons;
	}

	public function getCurrency(): string {
		return function_exists( 'get_woocommerce_currency' ) ? get_woocommerce_currency() : 'usd';
	}

	public function getCustomerInfo(): array {
		return [
			'first_name' => $this->order->get_billing_first_name(),
			'last_name'  => $this->order->get_billing_last_name(),
			'email'      => $this->order->get_billing_email(),
		];
	}

	public function isTransferProcessed(): bool {
		$value = $this->getOrderMeta( 'is_transfer_process_done' );
		return ! empty( $value );
	}

	/**
	 * Read `is_transfer_process_done` FRESH from the database, bypassing the
	 * persistent object cache (Redis on Cloudways) that get_meta()/get_post_meta()
	 * are served from. 3.7.5 (sc-DUP-XFER).
	 *
	 * This is the load-bearing guard for the duplicate-transfer fix: the second of
	 * two near-simultaneous webhook deliveries built its WCOrderAdapter from a
	 * cached wc_get_order() object whose meta still said "not done", so the
	 * pre-3.7.5 outside-the-lock guard let both deliveries run transfers and the
	 * vendor was paid twice (order 2288). TransferEngine now calls this INSIDE the
	 * per-order MySQL lock, so the winner of the lock has already committed the
	 * done-flag by the time the loser reads it straight from the table.
	 *
	 * Direct SELECT against the canonical meta table (HPOS `wc_orders_meta` or
	 * legacy `postmeta`) is the one read WordPress/WooCommerce do not cache. Falls
	 * back to the cached read if $wpdb is unavailable (e.g. unit tests).
	 */
	public function isTransferProcessedFresh(): bool {
		global $wpdb;
		if ( ! isset( $wpdb ) || ! is_object( $wpdb ) ) {
			return $this->isTransferProcessed();
		}

		$orderId = $this->order->get_id();

		if ( $this->isHposEnabled() ) {
			$table = $wpdb->prefix . 'wc_orders_meta';
			$value = $wpdb->get_var(
				$wpdb->prepare(
					// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $table is a $wpdb->prefix-derived constant identifier, not user input.
					"SELECT meta_value FROM {$table} WHERE order_id = %d AND meta_key = %s ORDER BY id DESC LIMIT 1",
					$orderId,
					'is_transfer_process_done'
				)
			);
		} else {
			$table = $wpdb->postmeta;
			$value = $wpdb->get_var(
				$wpdb->prepare(
					// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $wpdb->postmeta is a core table identifier, not user input.
					"SELECT meta_value FROM {$table} WHERE post_id = %d AND meta_key = %s ORDER BY meta_id DESC LIMIT 1",
					$orderId,
					'is_transfer_process_done'
				)
			);
		}

		return ! empty( $value );
	}

	public function markTransferProcessed(): void {
		$this->setOrderMeta( 'is_transfer_process_done', '1' );
	}

	public function addOrderNote( string $note ): void {
		$this->order->add_order_note( $note );
	}

	/** @return mixed */
	public function getOrderMeta( string $key ) {
		if ( $this->isHposEnabled() ) {
			return $this->order->get_meta( $key, true );
		}

		return get_post_meta( $this->order->get_id(), $key, true );
	}

	public function setOrderMeta( string $key, $value ): void {
		if ( $this->isHposEnabled() ) {
			$this->order->update_meta_data( $key, $value );
			$this->order->save();
			return;
		}

		update_post_meta( $this->order->get_id(), $key, $value );
	}

	public function getRawOrder(): \WC_Order {
		return $this->order;
	}

	public function getRequestBody(): string {
		return $this->requestBody;
	}

	private function isHposEnabled(): bool {
		return class_exists( OrderUtil::class ) && OrderUtil::custom_orders_table_usage_is_enabled();
	}
}
