<?php
/********************************************************************
 * Copyright (C) 2024 Darko Gjorgjijoski (https://darkog.com/)
 * Copyright (C) 2024 IDEOLOGIX MEDIA Dooel (https://ideologix.com/)
 *
 * This file is property of IDEOLOGIX MEDIA Dooel (https://ideologix.com)
 * This file is part of Vimeify Plugin - https://wordpress.org/plugins/vimeify/
 *
 * Vimeify - Formerly "WP Vimeo Videos" is free software: you can redistribute
 * it and/or modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, either version 2 of the License,
 * or (at your option) any later version.
 *
 * Vimeify - Formerly "WP Vimeo Videos" is distributed in the hope that it
 * will be useful, but WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this plugin. If not, see <https://www.gnu.org/licenses/>.
 *
 * Code developed by Darko Gjorgjijoski <dg@darkog.com>.
 **********************************************************************/

namespace Vimeify\Core\Utilities;

use Vimeify\Core\Utilities\Formatters\VimeoFormatter;
use Vimeify\Core\Utilities\Validators\CoreValidator;

/**
 * Class UploadQueue
 *
 * Handles background video uploads using Action Scheduler.
 * Replaces the unreliable WP_Background_Process with Action Scheduler's
 * built-in retry mechanism and better reliability.
 *
 * @since 2.0.0
 */
class UploadQueue {

	/**
	 * The Action Scheduler group name
	 */
	const GROUP = 'vimeify_uploads';

	/**
	 * Action hook for processing uploads
	 */
	const HOOK_PROCESS_UPLOAD = 'vimeify_process_upload';

	/**
	 * Maximum retries before giving up
	 */
	const MAX_RETRIES = 3;

	/**
	 * Whether hooks are registered
	 * @var bool
	 */
	private static $hooks_registered = false;

	/**
	 * Register Action Scheduler hooks
	 *
	 * @return void
	 */
	public static function register_hooks() {
		if ( self::$hooks_registered ) {
			return;
		}

		add_action( self::HOOK_PROCESS_UPLOAD, array( __CLASS__, 'process_upload' ), 10, 1 );

		self::$hooks_registered = true;
	}

	/**
	 * Queue a video upload for background processing
	 *
	 * @param array $item Upload data containing:
	 *   - tmp_path: Path to the temporary video file
	 *   - attempt: The Vimeo upload attempt data
	 *   - video_title: Title for the video
	 *   - video_description: Description for the video
	 *   - source: Array with source info (software, entry_id, field_id, form_id)
	 *   - entry_id: (optional) Form entry ID
	 *   - field_id: (optional) Form field ID
	 *   - form_id: (optional) Form ID
	 *
	 * @return int|false The action ID on success, false on failure
	 */
	public static function queue( $item ) {
		if ( ! function_exists( 'as_schedule_single_action' ) ) {
			self::log( 'Action Scheduler not available. Cannot queue upload.', 'UPLOAD-QUEUE-ERROR' );
			return false;
		}

		// Generate a unique upload ID for tracking
		$upload_id = self::generate_upload_id( $item );
		$item['upload_id'] = $upload_id;
		$item['retry_count'] = 0;
		$item['queued_at'] = time();

		// Schedule the upload action to run immediately
		$action_id = as_schedule_single_action(
			time(),
			self::HOOK_PROCESS_UPLOAD,
			array( $item ),
			self::GROUP
		);

		if ( $action_id ) {
			self::log( sprintf( 'Queued upload action #%d for upload %s', $action_id, $upload_id ), 'UPLOAD-QUEUE' );
		}

		return $action_id;
	}

	/**
	 * Process a queued upload
	 *
	 * This is the Action Scheduler callback that handles the actual upload.
	 *
	 * @param array $item Upload data
	 *
	 * @return void
	 */
	public static function process_upload( $item ) {
		$upload_id = isset( $item['upload_id'] ) ? $item['upload_id'] : 'unknown';
		$logtag = 'VIMEIFY-UPLOAD-QUEUE';

		self::log( sprintf( 'Processing upload %s', $upload_id ), $logtag );

		// Validate item type
		if ( ! is_array( $item ) ) {
			self::log( 'Invalid upload data (not an array)', $logtag );
			return;
		}

		// Validate video file
		$tmp_path = isset( $item['tmp_path'] ) ? $item['tmp_path'] : '';
		if ( empty( $tmp_path ) || ! file_exists( $tmp_path ) ) {
			// Also check 'data.path' format used by GravityForms
			if ( isset( $item['data']['path'] ) && file_exists( $item['data']['path'] ) ) {
				$tmp_path = $item['data']['path'];
			} else {
				self::log( sprintf( 'Video file not found: %s', $tmp_path ), $logtag );
				return;
			}
		}

		// Validate and retrieve the attempt
		$attempt = isset( $item['attempt'] ) ? $item['attempt'] : array();
		if ( isset( $item['data']['attempt'] ) ) {
			$attempt = $item['data']['attempt'];
		}
		if ( ! is_array( $attempt ) || empty( $attempt ) ) {
			self::log( 'Upload attempt data not found', $logtag );
			return;
		}

		// Get title and description
		$vimeo_title = isset( $item['video_title'] ) ? $item['video_title'] : '';
		if ( empty( $vimeo_title ) && isset( $item['data']['title'] ) ) {
			$vimeo_title = $item['data']['title'];
		}
		if ( empty( $vimeo_title ) ) {
			$vimeo_title = __( 'Untitled video', 'vimeify' );
		}

		$vimeo_description = isset( $item['video_description'] ) ? $item['video_description'] : '';
		if ( empty( $vimeo_description ) && isset( $item['data']['description'] ) ) {
			$vimeo_description = $item['data']['description'];
		}

		// Raise memory/time limits
		$core_validator = new CoreValidator();
		if ( $core_validator->is_function_available( 'set_time_limit' ) ) {
			set_time_limit( 0 );
		} else {
			self::log( 'Function set_time_limit is not available', $logtag );
		}
		wp_raise_memory_limit( 'image' );

		// Get plugin instance
		$plugin = self::get_plugin();
		if ( ! $plugin ) {
			self::log( 'Could not get plugin instance', $logtag );
			return;
		}

		try {
			$vimeo_formatter = new VimeoFormatter();

			// Finish the upload attempt
			self::log( sprintf( 'Finishing upload attempt for %s', $tmp_path ), $logtag );
			$response = $plugin->system()->vimeo()->finish_attempt( $tmp_path, $attempt );
			$vimeo_id = $vimeo_formatter->uri_to_id( $response );

			self::log( sprintf( 'Attempt finished. Response: %s', is_string( $response ) ? $response : wp_json_encode( $response ) ), $logtag );

			// Validate response
			if ( empty( $vimeo_id ) ) {
				self::log( 'Received empty vimeo_id response', $logtag );
				self::maybe_retry( $item, 'Empty response from Vimeo' );
				return;
			}

			// Get file size before deletion
			$file_size = filesize( $tmp_path );

			// Remove the file from local storage
			if ( unlink( $tmp_path ) ) {
				self::log( sprintf( 'Deleted temporary file: %s', $tmp_path ), $logtag );
			}

			// Gather source info
			$source = isset( $item['source'] ) ? $item['source'] : array();
			if ( empty( $source ) ) {
				$source = array(
					'software' => 'UploadQueue',
					'entry_id' => isset( $item['entry_id'] ) ? $item['entry_id'] : null,
					'field_id' => isset( $item['field_id'] ) ? $item['field_id'] : null,
					'form_id'  => isset( $item['form_id'] ) ? $item['form_id'] : null,
				);
			}

			// Fire success hooks
			$hook_data = array(
				'vimeo_title'       => $vimeo_title,
				'vimeo_description' => $vimeo_description,
				'vimeo_id'          => $vimeo_id,
				'vimeo_size'        => $file_size,
				'source'            => $source,
			);

			self::log( sprintf( 'Upload complete. Vimeo ID: %s', $vimeo_id ), $logtag );

			do_action( 'vimeify_frontend_upload_complete', $hook_data );

			// Fire integration-specific hooks based on source
			$software = isset( $source['software'] ) ? $source['software'] : '';
			if ( strpos( $software, 'WPForms' ) !== false ) {
				do_action( 'vimeify_frontend_upload_complete_wpforms', $hook_data );
				self::mark_wpforms_entry_finished( $item );
			} elseif ( strpos( $software, 'GravityForms' ) !== false ) {
				do_action( 'vimeify_frontend_upload_complete_gravityforms', $hook_data );
				self::update_gravityforms_entry( $item, $vimeo_id );
			} elseif ( strpos( $software, 'CustomForms' ) !== false ) {
				do_action( 'vimeify_frontend_upload_complete_customforms', $hook_data );
			}

		} catch ( \Exception $e ) {
			$error_msg = $e->getMessage() . ' in ' . $e->getFile() . ' on line ' . $e->getLine();
			self::log( sprintf( 'Upload failed: %s', $error_msg ), $logtag );

			// Attempt retry
			self::maybe_retry( $item, $e->getMessage() );
		}
	}

	/**
	 * Attempt to retry a failed upload
	 *
	 * @param array $item The upload item
	 * @param string $error_message The error that caused the failure
	 *
	 * @return void
	 */
	private static function maybe_retry( $item, $error_message ) {
		$retry_count = isset( $item['retry_count'] ) ? (int) $item['retry_count'] : 0;
		$upload_id = isset( $item['upload_id'] ) ? $item['upload_id'] : 'unknown';

		if ( $retry_count >= self::MAX_RETRIES ) {
			self::log( sprintf( 'Upload %s failed after %d retries. Error: %s', $upload_id, $retry_count, $error_message ), 'UPLOAD-QUEUE-FAILED' );

			// Fire failure hook
			do_action( 'vimeify_upload_queue_failed', $item, $error_message );
			return;
		}

		// Schedule retry with exponential backoff
		$delay = pow( 2, $retry_count ) * 60; // 1min, 2min, 4min
		$item['retry_count'] = $retry_count + 1;
		$item['last_error'] = $error_message;

		$action_id = as_schedule_single_action(
			time() + $delay,
			self::HOOK_PROCESS_UPLOAD,
			array( $item ),
			self::GROUP
		);

		self::log( sprintf( 'Scheduled retry #%d for upload %s in %d seconds (action #%d)', $item['retry_count'], $upload_id, $delay, $action_id ), 'UPLOAD-QUEUE' );
	}

	/**
	 * Get the status of pending uploads
	 *
	 * @param string|null $upload_id Optional specific upload ID to check
	 *
	 * @return array Array of pending upload statuses
	 */
	public static function get_status( $upload_id = null ) {
		if ( ! function_exists( 'as_get_scheduled_actions' ) ) {
			return array();
		}

		$args = array(
			'hook'   => self::HOOK_PROCESS_UPLOAD,
			'group'  => self::GROUP,
			'status' => \ActionScheduler_Store::STATUS_PENDING,
		);

		$actions = as_get_scheduled_actions( $args, 'objects' );
		$statuses = array();

		foreach ( $actions as $action ) {
			$action_args = $action->get_args();
			if ( ! empty( $action_args[0] ) ) {
				$item = $action_args[0];
				$item_upload_id = isset( $item['upload_id'] ) ? $item['upload_id'] : null;

				if ( $upload_id && $item_upload_id !== $upload_id ) {
					continue;
				}

				$statuses[] = array(
					'upload_id'   => $item_upload_id,
					'action_id'   => $action->get_id(),
					'status'      => 'pending',
					'retry_count' => isset( $item['retry_count'] ) ? $item['retry_count'] : 0,
					'queued_at'   => isset( $item['queued_at'] ) ? $item['queued_at'] : null,
					'scheduled'   => $action->get_schedule()->get_date()->getTimestamp(),
				);
			}
		}

		// Also check running actions
		$args['status'] = \ActionScheduler_Store::STATUS_RUNNING;
		$running_actions = as_get_scheduled_actions( $args, 'objects' );

		foreach ( $running_actions as $action ) {
			$action_args = $action->get_args();
			if ( ! empty( $action_args[0] ) ) {
				$item = $action_args[0];
				$item_upload_id = isset( $item['upload_id'] ) ? $item['upload_id'] : null;

				if ( $upload_id && $item_upload_id !== $upload_id ) {
					continue;
				}

				$statuses[] = array(
					'upload_id'   => $item_upload_id,
					'action_id'   => $action->get_id(),
					'status'      => 'running',
					'retry_count' => isset( $item['retry_count'] ) ? $item['retry_count'] : 0,
					'queued_at'   => isset( $item['queued_at'] ) ? $item['queued_at'] : null,
				);
			}
		}

		return $statuses;
	}

	/**
	 * Cancel a pending upload
	 *
	 * @param string $upload_id The upload ID to cancel
	 *
	 * @return bool True if cancelled, false if not found
	 */
	public static function cancel( $upload_id ) {
		if ( ! function_exists( 'as_unschedule_action' ) ) {
			return false;
		}

		$statuses = self::get_status( $upload_id );

		if ( empty( $statuses ) ) {
			return false;
		}

		$cancelled = false;
		foreach ( $statuses as $status ) {
			if ( $status['status'] === 'pending' ) {
				// We need to find and unschedule by the exact args
				$args = array(
					'hook'  => self::HOOK_PROCESS_UPLOAD,
					'group' => self::GROUP,
				);
				as_unschedule_all_actions( self::HOOK_PROCESS_UPLOAD, null, self::GROUP );
				$cancelled = true;

				self::log( sprintf( 'Cancelled upload %s', $upload_id ), 'UPLOAD-QUEUE' );
			}
		}

		return $cancelled;
	}

	/**
	 * Cancel all pending uploads
	 *
	 * @return int Number of cancelled uploads
	 */
	public static function cancel_all() {
		if ( ! function_exists( 'as_unschedule_all_actions' ) ) {
			return 0;
		}

		$statuses = self::get_status();
		$count = count( $statuses );

		as_unschedule_all_actions( self::HOOK_PROCESS_UPLOAD, null, self::GROUP );

		self::log( sprintf( 'Cancelled all %d pending uploads', $count ), 'UPLOAD-QUEUE' );

		return $count;
	}

	/**
	 * Generate a unique upload ID
	 *
	 * @param array $item Upload item data
	 *
	 * @return string
	 */
	private static function generate_upload_id( $item ) {
		$parts = array(
			isset( $item['form_id'] ) ? $item['form_id'] : '',
			isset( $item['field_id'] ) ? $item['field_id'] : '',
			isset( $item['entry_id'] ) ? $item['entry_id'] : '',
			microtime( true ),
		);

		return 'upload_' . substr( md5( implode( '_', $parts ) ), 0, 12 );
	}

	/**
	 * Mark WPForms entry as finished
	 *
	 * @param array $item Upload item data
	 *
	 * @return void
	 */
	private static function mark_wpforms_entry_finished( $item ) {
		$entry_id = isset( $item['entry_id'] ) ? $item['entry_id'] : null;
		$field_id = isset( $item['field_id'] ) ? $item['field_id'] : null;

		if ( empty( $entry_id ) || empty( $field_id ) ) {
			return;
		}

		if ( ! function_exists( 'wpforms' ) || ! property_exists( wpforms(), 'entry' ) ) {
			return;
		}

		$entry = wpforms()->entry->get( $entry_id );
		if ( is_null( $entry ) ) {
			return;
		}

		$fields = json_decode( $entry->fields, true );
		if ( ! is_array( $fields ) ) {
			return;
		}

		$changed = false;
		foreach ( $fields as &$field ) {
			if ( (int) $field['id'] === (int) $field_id ) {
				$field['is_finished'] = true;
				if ( isset( $field['attempt'] ) ) {
					unset( $field['attempt'] );
					$changed = true;
				}
				if ( isset( $field['tmp_path'] ) ) {
					unset( $field['tmp_path'] );
					$changed = true;
				}
				if ( isset( $field['errors'] ) && $field['errors'] === 'a:0:{}' ) {
					unset( $field['errors'] );
					$changed = true;
				}
			}
		}

		if ( $changed ) {
			wpforms()->entry->update( $entry_id, array( 'fields' => json_encode( $fields ) ) );
		}
	}

	/**
	 * Update GravityForms entry with vimeo ID
	 *
	 * @param array $item Upload item data
	 * @param string $vimeo_id The Vimeo video ID
	 *
	 * @return void
	 */
	private static function update_gravityforms_entry( $item, $vimeo_id ) {
		$entry_id = isset( $item['entry_id'] ) ? $item['entry_id'] : null;
		$field_id = isset( $item['field_id'] ) ? $item['field_id'] : null;

		if ( empty( $entry_id ) || empty( $field_id ) ) {
			return;
		}

		if ( ! function_exists( 'gform_update_meta' ) ) {
			return;
		}

		$data = isset( $item['data'] ) ? $item['data'] : array();

		// Remove attempt data
		if ( isset( $data['attempt'] ) ) {
			unset( $data['attempt'] );
		}

		// Clear path since file is deleted
		$data['path'] = null;
		if ( isset( $data['url'] ) ) {
			$data['url'] = null;
		}

		// Set vimeo ID
		$data['vimeo'] = $vimeo_id;

		gform_update_meta( $entry_id, $field_id, json_encode( array(
			'url'         => isset( $data['url'] ) ? $data['url'] : null,
			'path'        => isset( $data['path'] ) ? $data['path'] : null,
			'title'       => isset( $data['title'] ) ? $data['title'] : null,
			'description' => isset( $data['description'] ) ? $data['description'] : null,
			'vimeo'       => isset( $data['vimeo'] ) ? $data['vimeo'] : null,
			'error'       => isset( $data['error'] ) ? $data['error'] : null,
		) ) );
	}

	/**
	 * Get the plugin instance
	 *
	 * @return \Vimeify\Core\Plugin|\Vimeify\Pro\Plugin|null
	 */
	private static function get_plugin() {
		if ( function_exists( 'vimeify_pro' ) ) {
			return vimeify_pro()->plugin();
		}
		if ( function_exists( 'vimeify' ) ) {
			return vimeify();
		}
		return null;
	}

	/**
	 * Log a message
	 *
	 * @param string $message
	 * @param string $tag
	 *
	 * @return void
	 */
	private static function log( $message, $tag = 'UPLOAD-QUEUE' ) {
		$plugin = self::get_plugin();
		if ( $plugin && method_exists( $plugin, 'system' ) ) {
			$plugin->system()->logger()->log( $message, $tag );
		}
	}
}
