<?php
// DESCRIPTION: Core backfill logic — queries existing WordPress entities by hook type,
// builds value arrays matching the sync pipeline, and batch-appends rows to Google Sheets.

namespace SheetWise\Backfill;

use SheetWise\Admin\GoogleSheet;
use SheetWise\Sync\RetryHandler;
use SheetWise\Vendor\Google\Service\Sheets\ValueRange;

class BackfillProcessor {

	/**
	 * Number of records to process per batch
	 *
	 * @since 2.0.0
	 *
	 * @var int
	 */
	const BATCH_SIZE = 25;

	/**
	 * Hooks that support historical backfill
	 *
	 * @since 2.0.0
	 *
	 * @var array
	 */
	const BACKFILL_HOOKS = [
		'user_register',
		'wp_update_user',
		'save_post',
		'edit_post',
		'wp_insert_comment',
		'edit_comment',
		'woocommerce_new_product',
		'woocommerce_update_product',
		'woocommerce_update_order',
	];

	/**
	 * Check if a hook supports backfill
	 *
	 * @since 2.0.0
	 *
	 * @param string $hook
	 *
	 * @return bool
	 */
	public function supports_backfill( $hook ) {
		$hooks = apply_filters( 'swise_backfillable_hooks', self::BACKFILL_HOOKS );

		return in_array( $hook, $hooks, true );
	}

	/**
	 * Count total records available for backfill
	 *
	 * @since 2.0.0
	 *
	 * @param string $hook
	 *
	 * @return int
	 */
	public function count_records( $hook ) {
		$type = $this->get_entity_type( $hook );

		switch ( $type ) {
			case 'user':
				$query = new \WP_User_Query( [
					'count_total' => true,
					'number'      => 0,
				] );

				return $query->get_total();

			case 'post':
				$counts = wp_count_posts( 'post' );

				return isset( $counts->publish ) ? (int) $counts->publish : 0;

			case 'comment':
				$counts = wp_count_comments();

				return isset( $counts->approved ) ? (int) $counts->approved : 0;

			case 'wc_product':
				if ( ! function_exists( 'wc_get_products' ) ) {
					return 0;
				}

				$counts = wp_count_posts( 'product' );

				return isset( $counts->publish ) ? (int) $counts->publish : 0;

			case 'wc_order':
				if ( ! function_exists( 'wc_get_orders' ) ) {
					return 0;
				}

				return count( wc_get_orders( [
					'return' => 'ids',
					'limit'  => -1,
					'status' => array_keys( wc_get_order_statuses() ),
				] ) );

			default:
				return 0;
		}
	}

	/**
	 * Fetch a batch of entities for backfill
	 *
	 * @since 2.0.0
	 *
	 * @param string $hook
	 * @param int    $offset
	 * @param int    $limit
	 *
	 * @return array
	 */
	public function get_batch( $hook, $offset, $limit ) {
		$type = $this->get_entity_type( $hook );

		switch ( $type ) {
			case 'user':
				return get_users( [
					'number'  => $limit,
					'offset'  => $offset,
					'orderby' => 'ID',
					'order'   => 'ASC',
				] );

			case 'post':
				return get_posts( [
					'numberposts' => $limit,
					'offset'      => $offset,
					'orderby'     => 'ID',
					'order'       => 'ASC',
					'post_status' => 'publish',
					'post_type'   => swise_get_supported_post_types(),
				] );

			case 'comment':
				return get_comments( [
					'number'  => $limit,
					'offset'  => $offset,
					'orderby' => 'comment_ID',
					'order'   => 'ASC',
					'status'  => 'approve',
				] );

			case 'wc_product':
				if ( ! function_exists( 'wc_get_products' ) ) {
					return [];
				}

				return get_posts( [
					'numberposts' => $limit,
					'offset'      => $offset,
					'orderby'     => 'ID',
					'order'       => 'ASC',
					'post_type'   => 'product',
					'post_status' => 'publish',
				] );

			case 'wc_order':
				if ( ! function_exists( 'wc_get_orders' ) ) {
					return [];
				}

				return wc_get_orders( [
					'limit'   => $limit,
					'offset'  => $offset,
					'orderby' => 'ID',
					'order'   => 'ASC',
					'status'  => array_keys( wc_get_order_statuses() ),
				] );

			default:
				return [];
		}
	}

	/**
	 * Build values array for a single entity, matching the hook handler format
	 *
	 * @since 2.0.0
	 *
	 * @param string $hook
	 * @param mixed  $entity
	 *
	 * @return array Associative array of field_key => value
	 */
	public function build_values_for_entity( $hook, $entity ) {
		$type            = $this->get_entity_type( $hook );
		$all_event_codes = swise_get_data_source_events();

		if ( ! isset( $all_event_codes[ $hook ] ) ) {
			return [];
		}

		$event_keys = array_keys( $all_event_codes[ $hook ] );

		switch ( $type ) {
			case 'user':
				return $this->build_user_values( $entity, $event_keys );

			case 'post':
				return $this->build_post_values( $entity, $event_keys );

			case 'comment':
				return $this->build_comment_values( $entity, $event_keys );

			case 'wc_product':
				return $this->build_wc_product_values( $entity, $event_keys );

			case 'wc_order':
				return $this->build_wc_order_values( $entity );

			default:
				return [];
		}
	}

	/**
	 * Process a batch of records for a specific integration
	 *
	 * Builds row values for each entity and appends them all at once
	 * in a single Google Sheets API call.
	 *
	 * @since 2.0.0
	 *
	 * @param int $integration_id
	 * @param int $offset
	 *
	 * @return int Number of records processed
	 */
	public function process_batch( $integration_id, $offset ) {
		$integration = get_post( $integration_id );

		if ( ! $integration ) {
			throw new \Exception( __( 'Integration not found.', 'sheet-wise' ) );
		}

		$content = json_decode( $integration->post_content );

		if ( ! $content || empty( $content->source ) || empty( $content->sheet ) || empty( $content->event_codes ) ) {
			throw new \Exception( __( 'Invalid integration configuration.', 'sheet-wise' ) );
		}

		$hook           = $content->source;
		$spreadsheet_id = $content->sheet;
		$event_codes    = $content->event_codes;

		$entities = $this->get_batch( $hook, $offset, self::BATCH_SIZE );

		if ( empty( $entities ) ) {
			return 0;
		}

		$all_event_codes = swise_get_data_source_events();

		if ( ! isset( $all_event_codes[ $hook ] ) ) {
			throw new \Exception(
				sprintf(
					/* translators: %s: hook name */
					__( 'Unknown data source: %s', 'sheet-wise' ),
					$hook
				)
			);
		}

		$default_events = array_keys( $all_event_codes[ $hook ] );
		$placeholders   = array_map( function ( $event ) {
			return "[[$event]]";
		}, $default_events );

		// Build all rows for this batch
		$all_rows = [];

		foreach ( $entities as $entity ) {
			$values           = $this->build_values_for_entity( $hook, $entity );
			$submitted_values = [];

			foreach ( $default_events as $event ) {
				$submitted_values[] = isset( $values[ $event ] ) ? $values[ $event ] : '';
			}

			$row = [];

			foreach ( $event_codes as $event_code ) {
				$row[] = str_replace( $placeholders, $submitted_values, $event_code );
			}

			$all_rows[] = $row;
		}

		if ( empty( $all_rows ) ) {
			return 0;
		}

		// Append all rows in a single API call
		$sheet       = new GoogleSheet( $spreadsheet_id );
		$service     = $sheet->get_service();
		$sheet_title = apply_filters( 'swise_sheet_title_' . $hook, 'Sheet1', $spreadsheet_id );

		$value_range = new ValueRange();
		$value_range->setValues( $all_rows );

		$retry_handler = new RetryHandler();
		$context       = [
			'integration_id' => $integration_id,
			'hook'           => $hook,
			'data_payload'   => sprintf( 'Backfill batch: %d rows from offset %d', count( $all_rows ), $offset ),
			'spreadsheet_id' => $spreadsheet_id,
			'sheet_title'    => $sheet_title,
		];

		$sync_result = $retry_handler->execute(
			function () use ( $service, $spreadsheet_id, $sheet_title, $value_range ) {
				return $service->spreadsheets_values->append(
					$spreadsheet_id, $sheet_title, $value_range, [ 'valueInputOption' => 'USER_ENTERED' ]
				);
			},
			$context
		);

		do_action( 'swise_sync_completed', $sync_result, $context );

		if ( $sync_result['status'] !== 'success' ) {
			$error_msg = $sync_result['error']['message'] ?? __( 'Unknown error', 'sheet-wise' );

			throw new \Exception( $error_msg );
		}

		return count( $entities );
	}

	/**
	 * Schedule the next batch via Action Scheduler
	 *
	 * @since 2.0.0
	 *
	 * @param int $integration_id
	 * @param int $offset
	 *
	 * @return void
	 */
	public function schedule_next_batch( $integration_id, $offset ) {
		as_enqueue_async_action(
			'sheetwise_backfill_batch',
			[
				'args' => [
					'integration_id' => $integration_id,
					'offset'         => $offset,
				],
			],
			'swise_backfill',
			true
		);
	}

	/**
	 * Map a hook to its entity type
	 *
	 * @since 2.0.0
	 *
	 * @param string $hook
	 *
	 * @return string
	 */
	private function get_entity_type( $hook ) {
		$map = [
			'user_register'              => 'user',
			'wp_update_user'             => 'user',
			'save_post'                  => 'post',
			'edit_post'                  => 'post',
			'wp_insert_comment'          => 'comment',
			'edit_comment'               => 'comment',
			'woocommerce_new_product'    => 'wc_product',
			'woocommerce_update_product' => 'wc_product',
			'woocommerce_update_order'   => 'wc_order',
		];

		return isset( $map[ $hook ] ) ? $map[ $hook ] : '';
	}

	/**
	 * Build values array for a user entity
	 *
	 * Replicates the logic from Hooks::get_user_values()
	 *
	 * @since 2.0.0
	 *
	 * @param \WP_User $user
	 * @param array    $event_keys
	 *
	 * @return array
	 */
	private function build_user_values( $user, $event_keys ) {
		$values         = [];
		$user_meta_keys = array_keys( get_user_meta( $user->ID ) );

		foreach ( $event_keys as $event ) {
			if ( $event === 'user_id' ) {
				$values[ $event ] = $user->ID;
			} elseif ( in_array( $event, $user_meta_keys, true ) ) {
				$values[ $event ] = get_user_meta( $user->ID, $event, true );
			} elseif ( isset( $user->data->$event ) ) {
				$values[ $event ] = $user->data->$event;
			} else {
				$values[ $event ] = '';
			}
		}

		return $values;
	}

	/**
	 * Build values array for a post entity
	 *
	 * Posts are passed as WP_Post objects — properties map directly to event keys
	 *
	 * @since 2.0.0
	 *
	 * @param \WP_Post $post
	 * @param array    $event_keys
	 *
	 * @return array
	 */
	private function build_post_values( $post, $event_keys ) {
		$values    = [];
		$post_data = $post->to_array();

		foreach ( $event_keys as $event ) {
			if ( isset( $post_data[ $event ] ) ) {
				$values[ $event ] = $post_data[ $event ];
			} else {
				$values[ $event ] = get_post_meta( $post->ID, $event, true );
			}
		}

		return $values;
	}

	/**
	 * Build values array for a comment entity
	 *
	 * Comments are passed as WP_Comment objects — properties map directly to event keys
	 *
	 * @since 2.0.0
	 *
	 * @param \WP_Comment $comment
	 * @param array       $event_keys
	 *
	 * @return array
	 */
	private function build_comment_values( $comment, $event_keys ) {
		$values       = [];
		$comment_data = (array) $comment;

		foreach ( $event_keys as $event ) {
			if ( isset( $comment_data[ $event ] ) ) {
				$values[ $event ] = $comment_data[ $event ];
			} else {
				$values[ $event ] = get_comment_meta( $comment->comment_ID, $event, true );
			}
		}

		return $values;
	}

	/**
	 * Build values array for a WooCommerce product
	 *
	 * Replicates the logic from WooCommerce::woocommerce_update_product()
	 *
	 * @since 2.0.0
	 *
	 * @param \WP_Post $post
	 * @param array    $event_keys
	 *
	 * @return array
	 */
	private function build_wc_product_values( $post, $event_keys ) {
		if ( ! function_exists( 'wc_get_product' ) ) {
			return [];
		}

		$wc_product = wc_get_product( $post->ID );

		if ( ! $wc_product instanceof \WC_Product ) {
			return [];
		}

		$values = [
			'product_id'    => $wc_product->get_id(),
			'post_title'    => $wc_product->get_name(),
			'post_content'  => $wc_product->get_description(),
			'post_name'     => $wc_product->get_slug(),
			'post_date'     => $post->post_date,
			'post_modified' => $post->post_modified,
			'active_price'  => $wc_product->get_price(),
			'regular_price' => $wc_product->get_regular_price(),
			'sale_price'    => $wc_product->get_sale_price(),
			'slug'          => $wc_product->get_slug(),
			'date'          => $post->post_date,
		];

		// Only include keys that are in the event schema
		$result = [];

		foreach ( $event_keys as $key ) {
			$result[ $key ] = isset( $values[ $key ] ) ? $values[ $key ] : '';
		}

		return $result;
	}

	/**
	 * Build values array for a WooCommerce order
	 *
	 * Replicates the logic from WooCommerce::woocommerce_update_order()
	 *
	 * @since 2.0.0
	 *
	 * @param \WC_Order $order
	 *
	 * @return array
	 */
	private function build_wc_order_values( $order ) {
		if ( ! $order instanceof \WC_Order ) {
			return [];
		}

		$date_created = $order->get_date_created();

		return [
			'order_id'       => $order->get_id(),
			'order_status'   => $order->get_status(),
			'order_date'     => $date_created ? $date_created->date_i18n() : '',
			'order_total'    => $order->get_total(),
			'total_tax'      => $order->get_total_tax(),
			'currency'       => $order->get_currency(),
			'shipping_total' => $order->get_shipping_total(),
			'order_key'      => $order->get_order_key(),
		];
	}
}
