<?php
/**
 * Square Webhook Handler
 *
 * @package FunnelKit_Square
 */

namespace FKWCSQ\Includes\Webhooks;

use FKWCSQ\Includes\API;
use FKWCSQ\Includes\Logger;
use FKWCSQ\Includes\Traits\Instance;
use Exception;

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly
}

/**
 * Webhook Handler Class
 */
class Webhook_Handler {
	use Instance;

	/**
	 * API instance
	 *
	 * @var API
	 */
	protected $api;

	/**
	 * Constructor
	 */
	private function __construct() {
		$this->api = API::get_instance();
	}

	/**
	 * Initialize webhook handling
	 */
	public static function init() {
		$instance = self::get_instance();
		\add_action( 'rest_api_init', array( $instance, 'register_endpoints' ) );
	}

	/**
	 * Register webhook endpoints
	 */
	public function register_endpoints() {
		\register_rest_route(
			'fkwcsq',
			'/v1/webhook',
			array(
				'methods'             => 'POST',
				'callback'            => array( $this, 'handle_webhook' ),
				'permission_callback' => array( $this, 'verify_webhook' ),
			)
		);
	}

	/**
	 * Verify webhook request using the pre-shared auth key embedded by our relay server.
	 *
	 * Square's HMAC is verified at the relay server. The relay then forwards the request
	 * with our site-specific key in X-FKWCSQ-Webhook-Token so we can confirm the request
	 * genuinely came from our server and not an arbitrary third party.
	 *
	 * @param \WP_REST_Request $request The incoming REST request.
	 * @return true|\WP_Error
	 */
	public function verify_webhook( \WP_REST_Request $request ) {
		$key = get_option( 'fkwcsq_webhook_auth_key', '' );
		if ( empty( $key ) ) {
			Logger::error( 'Webhook auth key not configured — request rejected.' );
			return new \WP_Error( 'rest_forbidden', __( 'Webhook authentication is not configured.', 'funnelkit-payment-gateway-square-for-woocommerce' ), array( 'status' => 401 ) );
		}

		$token = $request->get_header( 'X-FKWCSQ-Webhook-Token' );
		if ( empty( $token ) ) {
			Logger::error( 'Webhook request missing X-FKWCSQ-Webhook-Token header.' );
			return new \WP_Error( 'rest_forbidden', __( 'Missing webhook authentication token.', 'funnelkit-payment-gateway-square-for-woocommerce' ), array( 'status' => 401 ) );
		}

		if ( ! hash_equals( $key, $token ) ) {
			Logger::error( 'Webhook token mismatch — possible forged request.' );
			return new \WP_Error( 'rest_forbidden', __( 'Invalid webhook authentication token.', 'funnelkit-payment-gateway-square-for-woocommerce' ), array( 'status' => 401 ) );
		}



		return true;
	}

	/**
	 * Handle incoming webhook
	 *
	 * @return \WP_REST_Response
	 * @throws Exception
	 */
	public function handle_webhook() {
		try {
			$payload = file_get_contents( 'php://input' );
			$data    = json_decode( $payload, true );

			if ( json_last_error() !== JSON_ERROR_NONE ) {
				throw new Exception( 'Invalid JSON payload' );
			}

			if ( ! isset( $data['type'] ) || ! isset( $data['data'] ) ) {
				throw new Exception( 'Missing required webhook data' );
			}

			switch ( $data['type'] ) {
				case 'inventory.count.updated':
					$this->handle_inventory_update( $data );
					break;
				case 'payment.updated':
					$this->handle_payment_updated( $data );
					break;
				case 'dispute.created':
					$this->handle_dispute_created( $data );
					break;
				case 'dispute.state.updated':
					$this->handle_dispute_updated( $data );
					break;
				default:
					break;
			}

			return new \WP_REST_Response( array( 'status' => 'success' ), 200 );

		} catch ( Exception $e ) {
			Logger::error( 'Webhook error: ' . $e->getMessage() );
			return new \WP_REST_Response(
				array(
					'status'  => 'error',
					'message' => 'Webhook processing failed.',
				),
				400
			);
		}
	}

	/**
	 * Handle inventory update webhook
	 *
	 * @param array $data Webhook data
	 * @throws Exception
	 */
	protected function handle_inventory_update( $data ) {
		if ( ! isset( $data['data']['object']['inventory_counts'] ) ) {
			throw new Exception( 'Missing inventory counts data' );
		}

		$current_location_id = $this->api->get_location_id();
		$inventory_counts    = $data['data']['object']['inventory_counts'];

		// Group counts by variation ID
		$counts_by_variation = array();
		foreach ( $inventory_counts as $count ) {
			if ( ! isset( $count['location_id'] ) || ! isset( $count['catalog_object_id'] ) || ! isset( $count['quantity'] ) ) {
				continue;
			}
			if ( $count['location_id'] !== $current_location_id ) {
				Logger::debug( 'Skipping inventory update for different location: ' . $count['location_id'] );
				continue;
			}
			$variation_id = $count['catalog_object_id'];
			if ( ! isset( $counts_by_variation[ $variation_id ] ) ) {
				$counts_by_variation[ $variation_id ] = array();
			}
			$counts_by_variation[ $variation_id ][] = $count;
		}

		foreach ( $counts_by_variation as $variation_id => $counts ) {
			$in_stock_count = null;
			$other_states   = array();
			foreach ( $counts as $count ) {
				if ( isset( $count['state'] ) && $count['state'] === 'IN_STOCK' ) {
					$in_stock_count = $count;
				} else {
					$other_states[] = $count;
				}
			}

			if ( $in_stock_count ) {
				$quantity = (float) $in_stock_count['quantity'];
				// Find WooCommerce product by Square variation ID
				$args       = array(
					'post_type'      => 'product',
					'meta_key'       => '_square_variation_id', //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Necessary for Square integration lookup
					'meta_value'     => $variation_id, //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value -- Necessary for Square integration lookup
					'posts_per_page' => 1,
				);
				$variations = \get_posts( $args );
				if ( empty( $variations ) ) {
					Logger::debug( 'No WooCommerce variation found for Square variation ID: ' . $variation_id );
					continue;
				}
				$variation = \wc_get_product( $variations[0]->ID );
				if ( ! $variation ) {
					Logger::error( 'Failed to load WooCommerce variation: ' . $variations[0]->ID );
					continue;
				}
				$variation->set_manage_stock( true );
				$variation->set_stock_quantity( $quantity );
				$variation->set_stock_status( $quantity > 0 ? 'instock' : 'outofstock' );
				$variation->save();
				Logger::debug( sprintf( 'Updated inventory for variation %d: quantity=%f, state=IN_STOCK', $variation->get_id(), $quantity ) );
			}
			// Log other states for reference
			foreach ( $other_states as $other ) {
				Logger::debug( sprintf( 'Received non-IN_STOCK state for variation %s: quantity=%f, state=%s', $variation_id, (float) $other['quantity'], isset( $other['state'] ) ? $other['state'] : 'UNKNOWN' ) );
			}
		}
	}

	/**
	 * Try to find WooCommerce product by Square item data.
	 *
	 * @param array $item Square line item
	 * @return int|false WooCommerce product ID or false if not found
	 */
	protected function find_wc_product_by_square_item( $item ) {
		if ( ! empty( $item['variation_name'] ) ) {
			$args     = array(
				'post_type'  => 'product_variation',
				'meta_key'   => '_sku', //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Necessary for SKU lookup
				'meta_value' => $item['variation_name'], //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value -- Necessary for SKU lookup
				'fields'     => 'ids',
			);
			$products = \get_posts( $args );
			if ( ! empty( $products ) ) {
				return $products[0];
			}
		}
		if ( ! empty( $item['catalog_object_id'] ) ) {
			$args     = array(
				'post_type'  => 'product',
				'meta_key'   => '_square_id', //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Necessary for Square integration lookup
				'meta_value' => $item['catalog_object_id'], //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value -- Necessary for Square integration lookup
				'fields'     => 'ids',
			);
			$products = \get_posts( $args );
			if ( ! empty( $products ) ) {
				return $products[0];
			}
		}
		return false;
	}

	/**
	 * Handle Square payment.updated webhook and create WooCommerce order if needed.
	 *
	 * @param array $data Webhook data
	 */
	protected function handle_payment_updated( $data ) {
		$payment             = $data['data']['object']['payment'];
		$current_location_id = $this->api->get_location_id();

		if ( empty( $payment['location_id'] ) || $payment['location_id'] !== $current_location_id ) {
			Logger::debug( 'Payment location_id does not match, skipping.' );
			return;
		}

		// If payment has reference_id, it originated from WooCommerce.
		// Check if it's an ACH payment that needs status update.
		if ( ! empty( $payment['reference_id'] ) ) {
			$this->handle_woocommerce_payment_update( $payment );
			return;
		}

		$settings    = $this->api->get_plugin_settings();
		$sync_orders = ! isset( $settings['sync_woocommerce_order_from_square'] ) || 'yes' === $settings['sync_woocommerce_order_from_square'];
		if ( ! $sync_orders ) {
			Logger::debug( 'Order creation from Square payment disabled in Square settings.' );
			return;
		}

		// Check for existing WooCommerce order
		$existing_orders = wc_get_orders(
			array(
				'meta_key'   => '_square_payment_id', //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Necessary for Square payment lookup
				'meta_value' => $payment['id'], //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value -- Necessary for Square payment lookup
				'limit'      => 1,
			)
		);
		if ( ! empty( $existing_orders ) ) {
			Logger::debug( 'WooCommerce order already exists for Square payment, skipping creation.' );
			return;
		}

		$order = wc_create_order();

		// Set payment method for orders created from Square
		$order->set_payment_method( 'fkwcsq_square' );
		$order->set_payment_method_title( __( 'Credit Card (Square)', 'funnelkit-payment-gateway-square-for-woocommerce' ) );

		// Add order note to identify Square-originated orders
		$order->add_order_note(
			__( 'Order created from Square payment received outside WooCommerce checkout.', 'funnelkit-payment-gateway-square-for-woocommerce' )
		);

		// Fetch Square order for line items
		$line_items = array();
		if ( ! empty( $payment['order_id'] ) ) {
			$square_order = $this->api->get_square_order( $payment['order_id'] );
			if ( is_object( $square_order ) && method_exists( $square_order, 'getLineItems' ) ) {
				$line_items = $square_order->getLineItems();
			} elseif ( is_array( $square_order ) && isset( $square_order['line_items'] ) ) {
				$line_items = $square_order['line_items'];
			}
		}

		$order_total = 0;
		foreach ( $line_items as $item ) {
			if ( is_object( $item ) ) {
				$item = json_decode( wp_json_encode( $item ), true );
			}
			$product_id = false;
			if ( ! empty( $item['catalog_object_id'] ) ) {
				$args     = array(
					'post_type'      => array( 'product', 'product_variation' ),
					'meta_query'     => array( //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Necessary for Square integration lookup
						'relation' => 'OR',
						array(
							'key'   => '_square_id',
							'value' => $item['catalog_object_id'],
						),
						array(
							'key'   => '_square_variation_id',
							'value' => $item['catalog_object_id'],
						),
					),
					'fields'         => 'ids',
					'posts_per_page' => 1,
				);
				$products = get_posts( $args );
				if ( ! empty( $products ) ) {
					$product_id = $products[0];
				}
			}
			$qty          = isset( $item['quantity'] ) ? (int) $item['quantity'] : 1;
			$price        = isset( $item['base_price_money']['amount'] ) ? $item['base_price_money']['amount'] / 100 : 0;
			$order_total += $price * $qty;
			if ( $product_id ) {
				$product = wc_get_product( $product_id );
				$order->add_product(
					$product,
					$qty,
					array(
						'subtotal' => $price * $qty,
						'total'    => $price * $qty,
					)
				);
			} else {
				$order->add_product(
					null,
					$qty,
					array(
						'name'     => $item['name'] ?? 'Square Item',
						'subtotal' => $price * $qty,
						'total'    => $price * $qty,
					)
				);
			}
		}

		// Set order total to match Square payment
		$paid_total = isset( $payment['amount_money']['amount'] ) ? $payment['amount_money']['amount'] / 100 : $order_total;
		$order->set_total( $paid_total );

		// Fetch and set customer details if available
		if ( ! empty( $payment['customer_id'] ) ) {
			$square_customer = $this->api->get_square_customer( $payment['customer_id'] );
			if ( $square_customer ) {
				$email      = $square_customer['email_address'] ?? '';
				$first_name = $square_customer['given_name'] ?? '';
				$last_name  = $square_customer['family_name'] ?? '';
				$order->set_billing_email( $email );
				$order->set_billing_first_name( $first_name );
				$order->set_billing_last_name( $last_name );
				// Optionally, assign to WooCommerce user if email matches
				if ( $email ) {
					$user = get_user_by( 'email', $email );
					if ( $user ) {
						$order->set_customer_id( $user->ID );
					}
				}
			}
		}

		// Set billing/shipping from payment if not set by customer
		if ( ! $order->get_billing_email() && ! empty( $payment['buyer_email_address'] ) ) {
			$order->set_billing_email( $payment['buyer_email_address'] );
		}
		if ( ! empty( $payment['billing_address'] ) ) {
			$billing = $payment['billing_address'];
			$order->set_billing_first_name( $billing['first_name'] ?? '' );
			$order->set_billing_last_name( $billing['last_name'] ?? '' );
			$order->set_billing_address_1( $billing['address_line_1'] ?? '' );
			$order->set_billing_city( $billing['locality'] ?? '' );
			$order->set_billing_state( $billing['administrative_district_level_1'] ?? '' );
			$order->set_billing_postcode( $billing['postal_code'] ?? '' );
			$order->set_billing_country( $billing['country'] ?? '' );
		}
		if ( ! empty( $payment['shipping_address'] ) ) {
			$shipping = $payment['shipping_address'];
			$order->set_shipping_address_1( $shipping['address_line_1'] ?? '' );
			$order->set_shipping_city( $shipping['locality'] ?? '' );
			$order->set_shipping_state( $shipping['administrative_district_level_1'] ?? '' );
			$order->set_shipping_postcode( $shipping['postal_code'] ?? '' );
			$order->set_shipping_country( $shipping['country'] ?? '' );
		}

		// Add order note and receipt URL
		if ( ! empty( $payment['note'] ) ) {
			$order->add_order_note( $payment['note'] );
		}

		// Save payment/order IDs in meta
		$order->update_meta_data( '_square_payment_id', $payment['id'] );
		if ( ! empty( $payment['order_id'] ) ) {
			$order->update_meta_data( '_square_order_id', $payment['order_id'] );
		}
		if ( ! empty( $payment['customer_id'] ) ) {
			$order->update_meta_data( '_square_customer_id', $payment['customer_id'] );
		}
		$order->save();
		if ( function_exists( 'WFOCU_Core' ) ) {
			remove_action( 'woocommerce_pre_payment_complete', array( WFOCU_Core()->public, 'maybe_setup_upsell' ), 99 );
		}
		// Mark as paid if payment status is COMPLETED
		if ( $payment['status'] === 'COMPLETED' ) {
			$order->payment_complete( $payment['id'] );
			Logger::debug( 'Marked WooCommerce order as paid: ' . $order->get_id() );
		}

		Logger::debug( 'Created WooCommerce order from Square payment: ' . $payment['id'] );
	}

	/**
	 * Handle Square dispute.created webhook
	 *
	 * @param array $data Webhook data
	 * @throws Exception
	 */
	protected function handle_dispute_created( $data ) {
		if ( ! isset( $data['data']['object']['dispute'] ) ) {
			throw new Exception( 'Missing dispute data in webhook' );
		}

		$dispute = $data['data']['object']['dispute'];
		Logger::debug( 'Square dispute created: ' . $dispute['dispute_id'] );

		// Find the associated WooCommerce order
		$order = $this->find_order_by_square_payment_id( $dispute['payment_id'] );
		if ( ! $order ) {
			Logger::error( 'Could not find WooCommerce order for Square payment ID: ' . $dispute['payment_id'] );
			return;
		}

		// Update order status to on-hold for dispute
		// translators: %s: Square dispute ID
		$order->update_status( 'on-hold', sprintf( __( 'This order is under dispute. Please respond via Square dashboard. (Dispute ID: %s)', 'funnelkit-payment-gateway-square-for-woocommerce' ), $dispute['dispute_id'] ) );
		$order->update_meta_data( 'fkwcsq_status_before_dispute', $order->get_status() );
		$order->update_meta_data( '_square_dispute_id', $dispute['dispute_id'] );
		$order->save();

		do_action( 'fkwcsq_dispute_created', $dispute, $order );
	}

	/**
	 * Handle Square dispute.state.updated webhook
	 *
	 * @param array $data Webhook data
	 * @throws Exception
	 */
	protected function handle_dispute_updated( $data ) {
		if ( ! isset( $data['data']['object']['dispute'] ) ) {
			throw new Exception( 'Missing dispute data in webhook' );
		}

		$dispute = $data['data']['object']['dispute'];
		Logger::debug( 'Square dispute updated: ' . $dispute['dispute_id'] . ' - Status: ' . $dispute['state'] );

		// Find the associated WooCommerce order
		$order = $this->find_order_by_square_payment_id( $dispute['payment_id'] );
		if ( ! $order ) {
			Logger::error( 'Could not find WooCommerce order for Square payment ID: ' . $dispute['payment_id'] );
			return;
		}

		// Handle different dispute states
		$message = '';
		$status  = $order->get_status();

		switch ( $dispute['state'] ) {
			case 'ACCEPTED':
				$message = __( 'The disputed order was accepted/lost.', 'funnelkit-payment-gateway-square-for-woocommerce' );
				$status  = 'failed';
				break;
			case 'WON':
				$message = __( 'The disputed order was resolved in your favour.', 'funnelkit-payment-gateway-square-for-woocommerce' );
				$status  = $order->get_meta( 'fkwcsq_status_before_dispute' ) ?: 'completed';
				break;
			case 'DISPUTE_EVIDENCE_REQUIRED':
				$message = __( 'Evidence is required for this dispute.', 'funnelkit-payment-gateway-square-for-woocommerce' );
				$status  = 'on-hold';
				break;
			case 'DISPUTE_EVIDENCE_SUBMITTED':
				$message = __( 'Evidence has been submitted for this dispute.', 'funnelkit-payment-gateway-square-for-woocommerce' );
				$status  = 'on-hold';
				break;
		}

		if ( $message ) {
			$order->update_status( $status, $message );
		}

		do_action( 'fkwcsq_dispute_state_updated', $dispute, $order, $dispute['state'] );
	}

	/**
	 * Handle payment status updates for WooCommerce orders.
	 *
	 * This handles ACH payments that start as PENDING and later become COMPLETED.
	 * When an ACH payment clears (2-3 business days), this updates the order status.
	 *
	 * @param array $payment Square payment data.
	 * @return void
	 */
	protected function handle_woocommerce_payment_update( $payment ) {
		// Parse reference_id — supports structured format "si:SITE_ID|oi:ORDER_ID" / "oi:ORDER_ID"
		// and the legacy plain numeric string.
		$reference_id   = $payment['reference_id'];
		$parsed_site_id = '';
		$order_id       = 0;

		foreach ( explode( '|', $reference_id ) as $part ) {
			if ( strpos( $part, 'si:' ) === 0 ) {
				$parsed_site_id = substr( $part, 3 );
			} elseif ( strpos( $part, 'oi:' ) === 0 ) {
				$order_id = (int) substr( $part, 3 );
			}
		}

		if ( ! $order_id || empty( $parsed_site_id ) ) {
			Logger::debug( 'Invalid reference_id in payment webhook — missing si: or oi: segment: ' . $reference_id );
			return;
		}

		$stored_site_id = get_option( 'fkwcsq_site_id', '' );
		if ( $stored_site_id !== $parsed_site_id ) {
			Logger::debug( 'Webhook reference_id site_id mismatch — ignoring. Expected: ' . $stored_site_id . ', got: ' . $parsed_site_id );
			return;
		}

		$order = wc_get_order( $order_id );
		if ( ! $order ) {
			Logger::debug( 'Order not found for reference_id: ' . $order_id );
			return;
		}

		// Check if this is an ACH payment by checking the source type.
		$source_type    = $payment['source_type'] ?? '';
		$payment_source = $order->get_meta( '_square_payment_source' );

		// Only process ACH payments.
		if ( 'BANK_ACCOUNT' !== $source_type && 'ACH' !== $payment_source ) {
			Logger::debug( 'Non-ACH payment update skipped for order: ' . $order_id );
			return;
		}

		$payment_status       = $payment['status'] ?? '';
		$current_order_status = $order->get_status();

		Logger::debug( sprintf( 'ACH payment status update: Order %d, Payment status: %s, Order status: %s', $order_id, $payment_status, $current_order_status ) );

		// Handle status changes.
		switch ( $payment_status ) {
			case 'COMPLETED':
				// ACH payment has cleared.
				if ( 'on-hold' === $current_order_status ) {
					$order->update_meta_data( '_square_payment_status', 'COMPLETED' );
					$order->save();

					// Complete the payment.
					$order->payment_complete( $payment['id'] );

					Logger::debug( 'ACH payment completed for order: ' . $order_id );

					/**
					 * Fires when an ACH payment completes.
					 *
					 * @param array     $payment Square payment data.
					 * @param \WC_Order $order   WooCommerce order.
					 */
					do_action( 'fkwcsq_ach_payment_completed', $payment, $order );
				}
				break;

			case 'FAILED':
				// ACH payment failed.
				$order->update_meta_data( '_square_payment_status', 'FAILED' );
				$order->update_status(
					'failed',
					__( 'Square ACH payment failed. The bank transfer was rejected.', 'funnelkit-payment-gateway-square-for-woocommerce' )
				);
				$order->save();

				Logger::debug( 'ACH payment failed for order: ' . $order_id );

				/**
				 * Fires when an ACH payment fails.
				 *
				 * @param array     $payment Square payment data.
				 * @param \WC_Order $order   WooCommerce order.
				 */
				do_action( 'fkwcsq_ach_payment_failed', $payment, $order );
				break;

			case 'CANCELED':
				// ACH payment canceled.
				$order->update_meta_data( '_square_payment_status', 'CANCELED' );
				$order->update_status(
					'cancelled',
					__( 'Square ACH payment was canceled.', 'funnelkit-payment-gateway-square-for-woocommerce' )
				);
				$order->save();

				Logger::debug( 'ACH payment canceled for order: ' . $order_id );

				/**
				 * Fires when an ACH payment is canceled.
				 *
				 * @param array     $payment Square payment data.
				 * @param \WC_Order $order   WooCommerce order.
				 */
				do_action( 'fkwcsq_ach_payment_canceled', $payment, $order );
				break;
		}
	}

	/**
	 * Find WooCommerce order by Square payment ID
	 *
	 * @param string $square_payment_id Square payment ID
	 * @return \WC_Order|false
	 */
	protected function find_order_by_square_payment_id( $square_payment_id ) {
		$orders = wc_get_orders(
			array(
				'meta_key'   => '_square_payment_id', //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Necessary for Square payment lookup
				'meta_value' => $square_payment_id, //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value -- Necessary for Square payment lookup
				'limit'      => 1,
			)
		);

		return ! empty( $orders ) ? $orders[0] : false;
	}
}
