<?php
/**
 * Idempotent WooCommerce inventory adjustments.
 *
 * @package ScheduledChanges
 */

namespace ScheduledChangesWooCommerce\CoreCompat\WooCommerce\Inventory;

use ScheduledChanges\Provider\OperationResult;
use ScheduledChangesWooCommerce\CoreCompat\WooCommerce\ProductSynchronizer;
use Throwable;

/**
 * Applies set or additive stock changes with a product-owned operation ledger.
 */
final class InventoryAdjustmentService {
	private ProductSynchronizer $synchronizer;
	public function __construct( ?ProductSynchronizer $synchronizer = null ) {
		$synchronizer     ??= new ProductSynchronizer();
		$this->synchronizer = $synchronizer;
	}

	public function apply(
		int $product_id,
		string $mode,
		int $amount,
		string $idempotency_key
	): OperationResult {
		if ( '' === trim( $idempotency_key ) ) {
			return OperationResult::failure( 'missing_idempotency_key', 'Inventory operations require an idempotency key.' );
		}

		if ( $amount < 0 ) {
			return OperationResult::failure( 'invalid_amount', 'Inventory operation amounts cannot be negative.' );
		}

		$product = wc_get_product( $product_id );

		if ( ! $product instanceof \WC_Product ) {
			return OperationResult::failure( 'product_not_found', 'WooCommerce product not found.' );
		}

		if ( ! $product->get_manage_stock( 'edit' ) ) {
			return OperationResult::failure( 'stock_not_managed', 'The product does not manage stock quantities.' );
		}

		$ledger_key = '_scheduled_changes_inventory_' . substr( hash( 'sha256', $idempotency_key ), 0, 32 );
		$record     = $product->get_meta( $ledger_key, true, 'edit' );

		if ( is_array( $record ) && isset( $record['result'] ) ) {
			if (
				(string) ( $record['mode'] ?? '' ) !== $mode
				|| (int) ( $record['amount'] ?? -1 ) !== $amount
			) {
				return OperationResult::failure(
					'idempotency_conflict',
					'The inventory idempotency key was already used with different parameters.'
				);
			}

			return OperationResult::success(
				'Inventory operation was already applied.',
				false,
				array( 'quantity' => (int) $record['result'] )
			);
		}

		$current = (int) ( $product->get_stock_quantity( 'edit' ) ?? 0 );
		switch ( $mode ) {
			case InventoryQuantityMode::SET:
				$next = $amount;
				break;
			case InventoryQuantityMode::INCREASE:
				$next = $current + $amount;
				break;
			case InventoryQuantityMode::DECREASE:
				$next = $current - $amount;
				break;
			default:
				return OperationResult::failure( 'inventory_mode_invalid', 'The requested inventory operation mode is not supported.' );
		}

		if ( $next < 0 ) {
			return OperationResult::failure( 'negative_stock', 'The adjustment would create a negative stock quantity.' );
		}

		try {
			$product->set_stock_quantity( $next );
			$product->add_meta_data(
				$ledger_key,
				array(
					'mode'       => $mode,
					'amount'     => $amount,
					'previous'   => $current,
					'result'     => $next,
					'applied_at' => gmdate( 'c' ),
				),
				true
			);
			$product->save();
			$this->synchronizer->synchronize( $product );
			$reloaded = wc_get_product( $product_id );

			if ( ! $reloaded instanceof \WC_Product || $next !== (int) $reloaded->get_stock_quantity( 'edit' ) ) {
				return OperationResult::failure( 'verification_failed', 'Inventory readback did not match the requested quantity.' );
			}

			return OperationResult::success(
				'Inventory quantity saved and verified.',
				$current !== $next,
				array(
					'previous' => $current,
					'quantity' => $next,
				)
			);
		} catch ( Throwable $throwable ) {
			return OperationResult::failure( 'inventory_write_failed', $throwable->getMessage() );
		}
	}
}
