<?php
/**
 * Stock restore points. Before a stocktake changes stock, the pre-change
 * quantities are saved here so the user can roll back at any time.
 *
 * @package Stocktake_For_WooCommerce
 */

defined( 'ABSPATH' ) || exit;

// phpcs:disable WordPress.DB.DirectDatabaseQuery -- custom-table plugin: the schema lives in our own stwc_* tables; every query is fully $wpdb->prepare()d with %i table identifiers.

class STWC_Backups {

	public static function backups_table() {
		global $wpdb;
		return $wpdb->prefix . 'stwc_backups';
	}

	public static function items_table() {
		global $wpdb;
		return $wpdb->prefix . 'stwc_backup_items';
	}

	/**
	 * Create a restore point.
	 *
	 * @param int    $session_id
	 * @param string $label
	 * @param array  $rows Each: [ product_id, variation_id, name, qty ] (qty = stock BEFORE the change).
	 * @return int|false Backup id.
	 */
	public static function create( $session_id, $label, $rows ) {
		global $wpdb;
		if ( empty( $rows ) ) {
			return false;
		}

		$header = $wpdb->insert(
			self::backups_table(),
			array(
				'session_id' => (int) $session_id,
				'label'      => $label,
				'item_count' => count( $rows ),
				'created_by' => get_current_user_id(),
				'created_at' => current_time( 'mysql' ),
			),
			array( '%d', '%s', '%d', '%d', '%s' )
		);
		$backup_id = (int) $wpdb->insert_id;
		if ( false === $header || ! $backup_id ) {
			return false;
		}

		// Every row must persist: a partial restore point silently loses the
		// pre-change quantity of the missing items, so the caller's "no restore
		// point, no apply" guard must see a failure and abort the whole apply.
		foreach ( $rows as $r ) {
			$ok = $wpdb->insert(
				self::items_table(),
				array(
					'backup_id'    => $backup_id,
					'product_id'   => (int) $r['product_id'],
					'variation_id' => (int) $r['variation_id'],
					'name'         => $r['name'],
					'qty'          => (float) $r['qty'],
					'applied_qty'  => isset( $r['applied_qty'] ) ? (float) $r['applied_qty'] : 0,
					'managed'      => isset( $r['managed'] ) ? (int) (bool) $r['managed'] : 1,
				),
				array( '%d', '%d', '%d', '%s', '%f', '%f', '%d' )
			);
			if ( false === $ok ) {
				self::delete( $backup_id );
				return false;
			}
		}

		return $backup_id;
	}

	/**
	 * @param int $id
	 * @return array|null
	 */
	public static function get( $id ) {
		global $wpdb;
		$row = $wpdb->get_row( $wpdb->prepare( 'SELECT * FROM %i WHERE id = %d', self::backups_table(), $id ), ARRAY_A );
		return $row ?: null;
	}

	/**
	 * @param int $backup_id
	 * @return array
	 */
	public static function items( $backup_id ) {
		global $wpdb;
		return $wpdb->get_results( $wpdb->prepare( 'SELECT * FROM %i WHERE backup_id = %d', self::items_table(), $backup_id ), ARRAY_A );
	}


	/**
	 * @param int $session_id
	 * @return int
	 */
	public static function count_for_session( $session_id ) {
		global $wpdb;
		return (int) $wpdb->get_var( $wpdb->prepare( 'SELECT COUNT(*) FROM %i WHERE session_id = %d', self::backups_table(), $session_id ) );
	}

	/**
	 * @param int $session_id
	 * @return array|null Latest backup row for the session.
	 */
	public static function latest_for_session( $session_id ) {
		global $wpdb;
		$row = $wpdb->get_row( $wpdb->prepare( 'SELECT * FROM %i WHERE session_id = %d ORDER BY id DESC LIMIT 1', self::backups_table(), $session_id ), ARRAY_A );
		return $row ?: null;
	}

	/**
	 * Restore a backup: set each product's stock back to the saved value.
	 *
	 * @param int $backup_id
	 * @return array { restored:int, errors:string[] }
	 */
	public static function restore( $backup_id ) {
		$backup = self::get( $backup_id );
		if ( ! $backup ) {
			return array( 'restored' => 0, 'errors' => array( __( 'Restore point not found.', 'stocktake-for-woocommerce' ) ) );
		}

		global $wpdb;
		$restored   = 0;
		$errors     = array();
		$session_id = (int) $backup['session_id'];

		foreach ( self::items( $backup_id ) as $it ) {
			$target  = $it['variation_id'] ? (int) $it['variation_id'] : (int) $it['product_id'];
			$product = wc_get_product( $target );
			if ( ! $product ) {
				$errors[] = $it['name'];
				continue;
			}
			$cur         = wc_stock_amount( $product->managing_stock() ? $product->get_stock_quantity() : 0 );
			$was_managed = ! isset( $it['managed'] ) || (int) $it['managed'] === 1;

			if ( $was_managed ) {
				if ( ! $product->managing_stock() ) {
					$product->set_manage_stock( true );
				}
				$product->set_stock_quantity( wc_stock_amount( $it['qty'] ) );
			} else {
				// The product was not stock-managed before the apply turned
				// management on — restore that state instead of a quantity.
				$product->set_manage_stock( false );
				$product->set_stock_quantity( null );
			}
			$product->save();
			$restored++;

			STWC_Log::add(
				array(
					'session_id'   => $session_id,
					'product_id'   => (int) $it['product_id'],
					'variation_id' => (int) $it['variation_id'],
					'name'         => $it['name'],
					'event'        => 'reverted',
					'qty_before'   => $cur,
					'qty_after'    => (float) $it['qty'],
				)
			);

			// Roll the matching session line back too: not applied any more, and
			// its applied portion returns to what it was before that apply (this
			// keeps delta math exact across undo/redo of accumulated waves).
			if ( $session_id ) {
				$wpdb->update(
					STWC_Sessions::items_table(),
					array(
						'applied'      => 0,
						'applied_from' => null,
						'applied_qty'  => isset( $it['applied_qty'] ) ? (float) $it['applied_qty'] : 0,
					),
					array(
						'session_id'   => $session_id,
						'product_id'   => (int) $it['product_id'],
						'variation_id' => (int) $it['variation_id'],
					),
					array( '%d', '%f', '%f' ),
					array( '%d', '%d', '%d' )
				);
			}
		}

		return array( 'restored' => $restored, 'errors' => $errors );
	}

	/**
	 * @param int $id
	 */
	public static function delete( $id ) {
		global $wpdb;
		$wpdb->delete( self::items_table(), array( 'backup_id' => $id ), array( '%d' ) );
		$wpdb->delete( self::backups_table(), array( 'id' => $id ), array( '%d' ) );
	}

	/**
	 * Remove a session's backups (called when a session is deleted).
	 *
	 * @param int $session_id
	 */
	public static function delete_for_session( $session_id ) {
		global $wpdb;
		$ids = $wpdb->get_col( $wpdb->prepare( 'SELECT id FROM %i WHERE session_id = %d', self::backups_table(), $session_id ) );
		foreach ( $ids as $id ) {
			self::delete( (int) $id );
		}
	}
}
