<?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;

class VimeoSync {

	const SAFE_REMAINING_API_REQUESTS_THRESHOLD_PAID = 10;
	const SAFE_REMAINING_API_REQUESTS_THRESHOLD_FREE = 20;

	const STATUS_SYNC_BATCH_SIZE_PAID = 50;
	const STATUS_SYNC_BATCH_SIZE_FREE = 10;

	const MAX_RETRY_ATTEMPTS = 3;
	const MAX_404_BEFORE_DELETE = 3;

	/**
	 * The sync process
	 * @var \Vimeify\Core\Plugin
	 */
	protected $plugin;

	/**
	 * Constructor
	 *
	 * @param  \Vimeify\Core\Abstracts\Interfaces\PluginInterface  $plugin
	 */
	public function __construct( &$plugin ) {
		$this->plugin = $plugin;
	}

	/**
	 * Get the safety threshold based on Vimeo account type
	 *
	 * @return int
	 */
	public function get_safety_threshold() {
		$default = $this->plugin->system()->vimeo()->is_free()
			? self::SAFE_REMAINING_API_REQUESTS_THRESHOLD_FREE
			: self::SAFE_REMAINING_API_REQUESTS_THRESHOLD_PAID;

		return apply_filters( 'vimeify_sync_safety_threshold', $default, $this->plugin );
	}

	/**
	 * Get the batch size for status sync based on Vimeo account type
	 *
	 * @return int
	 */
	public function get_status_sync_batch_size() {
		$default = $this->plugin->system()->vimeo()->is_free()
			? self::STATUS_SYNC_BATCH_SIZE_FREE
			: self::STATUS_SYNC_BATCH_SIZE_PAID;

		return apply_filters( 'vimeify_sync_batch_size', $default, $this->plugin );
	}

	/**
	 * Get the lock duration based on batch size
	 *
	 * Calculates a safe lock duration to prevent concurrent syncs while
	 * ensuring the lock doesn't expire mid-sync on slow hosts.
	 *
	 * @return int Lock duration in seconds
	 */
	public function get_lock_duration() {
		$batch_size = $this->get_status_sync_batch_size();
		// 2 seconds per video + 60 second buffer for slow hosts
		$duration = ( $batch_size * 2 ) + 60;

		return apply_filters( 'vimeify_sync_lock_duration', $duration, $batch_size, $this->plugin );
	}

	/**
	 * Sync the videos that were deleted.
	 * @return int
	 */
	public function sync_status( $fresh = false ) {

		$tag     = 'VIMEIFY-STATUS-SYNC';
		$deleted = 0;

		if ( $fresh ) {
			$this->delete_lock( 'status_sync' );
		}

		if ( $this->check_lock( 'status_sync' ) ) {
			$this->plugin->system()->logger()->log( 'Video status sync - Already in progress.', $tag, 'cron.log' );

			return $deleted;
		}

		if ( ! $this->plugin->system()->vimeo()->state->is_connected ) {
			$this->plugin->system()->logger()->log( 'Video status sync - API not connected, skipping sync.', $tag, 'cron.log' );

			return $deleted;
		}

		$this->set_lock( 'status_sync', $this->get_lock_duration() );

		$sync = $this->get_last_status_sync();
		if ( empty( $sync ) ) {
			$sync = [ 'videos' => [], 'last_pos' => 0, 'last_synced_at' => time() ];
			$this->plugin->system()->logger()->log( 'Video status sync - Syncing for the first time. Preparing...' );
		}

		if ( empty( $sync['videos'] ) ) {
			global $wpdb;
			$sync['videos'] = $wpdb->get_results( "SELECT PM.post_id, PM.meta_value as vimeo_id from $wpdb->postmeta PM WHERE PM.meta_key='vimeify_response' ORDER BY PM.meta_id ASC", ARRAY_A );
			$this->plugin->system()->logger()->log( 'Video status sync - No unsynced local videos found from previous iteration, restarting from scratch', $tag, 'cron.log' );
		}

		// Initialize retries tracking if not present
		if ( ! isset( $sync['retries'] ) ) {
			$sync['retries'] = array();
		}

		if ( ! empty( $sync['videos'] ) ) {

			$video_count      = count( $sync['videos'] );
			$batch_size       = $this->get_status_sync_batch_size();
			$safety_threshold = $this->get_safety_threshold();
			$processed_count  = 0;

			$this->plugin->system()->logger()->log( sprintf( 'Video status sync - Syncing %d videos, starting from position %d (batch size: %d, threshold: %d)', $video_count, $sync['last_pos'] + 1, $batch_size, $safety_threshold ), $tag, 'cron.log' );

			$call_quota = $this->plugin->system()->vimeo()->get_calls_quota();
			$max_calls  = $call_quota['limit']; // initial.
			for ( $i = $sync['last_pos'] + 1; $i < $video_count; $i ++ ) {

				if ( empty( $sync['videos'][ $i ]['vimeo_id'] ) ) {
					continue;
				}

				$post_id   = $sync['videos'][ $i ]['post_id'];
				$video_key = (string) $post_id;

				// Check if this video has exceeded max retry attempts
				if ( isset( $sync['retries'][ $video_key ] ) && $sync['retries'][ $video_key ] >= self::MAX_RETRY_ATTEMPTS ) {
					$this->plugin->system()->logger()->log( sprintf( 'Video status sync - Skipping video #%d after %d failed attempts', $post_id, self::MAX_RETRY_ATTEMPTS ), $tag, 'cron.log' );
					continue;
				}

				// Check batch limit
				if ( $processed_count >= $batch_size ) {
					$this->plugin->system()->logger()->log( sprintf( 'Video status sync - Reached batch limit (%d videos), continuing in the next iteration', $batch_size ), $tag, 'cron.log' );
					break;
				}

				// Save last position
				$sync['last_pos'] = $i;
				$this->plugin->system()->logger()->log( sprintf( 'Video status sync - Syncing local video #%d', $post_id ), $tag, 'cron.log' );

				// Break of the loop if reaching low api quota, try again later where you left off.
				if ( $max_calls <= $safety_threshold ) {
					$sync['last_pos'] --; // Re-attempt? - Yes.
					$this->plugin->system()->logger()->log( sprintf( 'Video status sync - Reached low api calls quota (%d, threshold: %d), continuing in the next iteration', $max_calls, $safety_threshold ), $tag, 'cron.log' );
					break;
				}
				try {
					$response = $this->plugin->system()->vimeo()->get_video_by_id( $sync['videos'][ $i ]['vimeo_id'] );

					// Update max requests
					if ( isset( $response['headers']['x-ratelimit-remaining'] ) ) {
						$max_calls = (int) $response['headers']['x-ratelimit-remaining'];
						$this->plugin->system()->logger()->log( sprintf( 'Video status sync - Remaining calls re-acquired (%d)', $max_calls ), $tag, 'cron.log' );
					} else {
						$this->plugin->system()->logger()->log( sprintf( 'Video status sync - Remaining calls reduced (%d)', $max_calls ), $tag, 'cron.log' );
						$max_calls --;
					}

					if ( isset( $response['status'] ) && $response['status'] === 429 ) {
						// Quota exceed!
						$this->plugin->system()->logger()->log( 'Video status sync - API Quota exceeded, continuing in the next iteration', $tag, 'cron.log' );
						$sync['last_pos'] --; // Re-attempt? - Yes.
						break;
					}
					if ( isset( $response['status'] ) && $response['status'] === 404 ) {
						// Quarantine system: require multiple consecutive 404s before deletion
						$fail_count = (int) get_post_meta( $post_id, '_vimeify_404_count', true );
						$fail_count ++;

						if ( $fail_count >= self::MAX_404_BEFORE_DELETE ) {
							$this->plugin->system()->database()->delete_local_video( $post_id );
							$this->plugin->system()->logger()->log( sprintf( 'Video status sync - Remote video not found %d times, deleted local video #%d', self::MAX_404_BEFORE_DELETE, $post_id ), $tag, 'cron.log' );
							$deleted ++;
						} else {
							update_post_meta( $post_id, '_vimeify_404_count', $fail_count );
							$this->plugin->system()->logger()->log( sprintf( 'Video status sync - Remote video not found (attempt %d/%d) for #%d', $fail_count, self::MAX_404_BEFORE_DELETE, $post_id ), $tag, 'cron.log' );
						}
					} else {
						// Video found - reset 404 counter if it exists
						delete_post_meta( $post_id, '_vimeify_404_count' );
						$this->plugin->system()->logger()->log( 'Video status sync - Remote video found, preserving local video', $tag, 'cron.log' );
					}

					// Clear retry counter on successful API call
					if ( isset( $sync['retries'][ $video_key ] ) ) {
						unset( $sync['retries'][ $video_key ] );
					}

				} catch ( \Exception $e ) {
					// Log the exception
					$this->plugin->system()->logger()->log(
						sprintf( 'Video status sync - Exception while syncing video #%d: %s', $post_id, $e->getMessage() ),
						$tag,
						'cron.log'
					);

					// Track retry attempts
					if ( ! isset( $sync['retries'][ $video_key ] ) ) {
						$sync['retries'][ $video_key ] = 0;
					}
					$sync['retries'][ $video_key ] ++;

					// Decrement position to retry this video next iteration
					$sync['last_pos'] --;

					// Save state and exit loop cleanly
					$this->set_last_status_sync( $sync );
					break;
				}
				$processed_count ++;
				$this->plugin->system()->logger()->log( sprintf( 'Video status sync - Just synced position %d', $sync['last_pos'] ), $tag, 'cron.log' );
			}

			$sync['last_synced_at'] = time();
			if ( ( $video_count - 1 ) === $sync['last_pos'] ) {
				$sync['videos']   = array();
				$sync['last_pos'] = 0;
				$sync['retries']  = array(); // Clear retries when starting fresh
				$this->plugin->system()->logger()->log( 'Video status sync - Reached end, restarting', $tag, 'cron.log' );
			}
			$this->set_last_status_sync( $sync );
		}

		$this->delete_lock( 'status_sync' );

		return $deleted;

	}

	/**
	 * Sync the videos metadata
	 * @return int
	 */
	public function sync_metadata( $fresh = false ) {

		$tag    = 'VIMEIFY-METADATA-SYNC';
		$synced = 0;

		if ( $fresh ) {
			$this->delete_lock( 'metadata_sync' );
		}

		if ( $this->check_lock( 'metadata_sync' ) ) {
			$this->plugin->system()->logger()->log( 'Metadata sync - Already in progress.', $tag, 'cron.log' );

			return $synced;
		}

		if ( ! $this->plugin->system()->vimeo()->state->is_connected ) {
			$this->plugin->system()->logger()->log( 'Metadata sync - API not connected, skipping sync.', $tag, 'cron.log' );

			return $synced;
		}

		$this->set_lock( 'metadata_sync' );

		// Get sync behavior setting
		$sync_behavior = $this->plugin->system()->settings()->global()->get( 'sync.sync_behavior', 'existing_only' );
		$create_new_videos = ( $sync_behavior === 'create_all' );

		$last_sync = $this->get_last_metadata_sync();

		if ( isset( $last_sync['latest_page'] ) && isset( $last_sync['total_pages'] ) && $last_sync['latest_page'] < $last_sync['total_pages'] ) {
			$next_page = $last_sync['latest_page'] + 1;
			$this->plugin->system()->logger()->log( sprintf( 'Metadata sync - Starting sync from where we last stopped at page %d (mode: %s)', $next_page, $sync_behavior ), $tag, 'cron.log' );
		} else {
			$next_page = 1;
			$this->plugin->system()->logger()->log( sprintf( 'Metadata sync - Starting sync from page %d (mode: %s)', 1, $sync_behavior ), $tag, 'cron.log' );
		}

		try {

			$result = $this->plugin->system()->vimeo()->get_uploaded_videos_safe( [
				'per_page'  => 100,
				'page'      => $next_page,
				'filter'    => 'playable',
				'sort'      => 'date',
				'direction' => 'asc',
				'fields'    => 'uri,name,description,size,duration,link,player_embed_url,width,height,is_playable,pictures.sizes,created_time',
			] );
			if ( ! empty( $result['videos'] ) ) {
				foreach ( $result['videos'] as $video ) {
					$synced ++;
					$post_id = $this->plugin->system()->database()->get_post_id( $video['uri'] );

					// If video doesn't exist locally
					if ( empty( $post_id ) ) {
						if ( $create_new_videos ) {
							// Create new video post
							$post_id = $this->create_video_from_vimeo( $video );
							if ( $post_id ) {
								$this->plugin->system()->logger()->log( sprintf( 'Metadata sync - Created new video #%d (%s)', $post_id, $video['uri'] ), $tag, 'cron.log' );
							} else {
								$this->plugin->system()->logger()->log( sprintf( 'Metadata sync - Failed to create video (%s)', $video['uri'] ), $tag, 'cron.log' );
								continue;
							}
						} else {
							// Skip videos that don't exist locally
							continue;
						}
					}

					// Update metadata for existing or newly created video
					$this->plugin->system()->vimeo()->set_local_video_metadata( $post_id, $video );
					$this->plugin->system()->logger()->log( sprintf( 'Metadata sync - Updated metadata for video #%d (%s)', $post_id, $video['uri'] ), $tag, 'cron.log' );
				}
			}

			$last_sync = $result;
			unset( $last_sync['videos'] );
			$this->set_last_metadata_sync( $last_sync );
			$this->plugin->system()->logger()->log( 'Metadata sync - Done for this iteration. Saved state.', $tag, 'cron.log' );


		} catch ( \Exception $e ) {
			$this->plugin->system()->logger()->log( 'Metadata sync - Error acquiring results from API: ' . $e->getMessage(), $tag, 'cron.log' );
			$this->delete_lock( 'metadata_sync' );
		}

		$this->delete_lock( 'metadata_sync' );

		return $synced;

	}

	/**
	 * Return the last sync details
	 * @return array
	 */
	private function get_last_metadata_sync() {
		$data = get_option( 'vimeify_last_metadata_sync' );
		if ( ! is_array( $data ) ) {
			$data = [];
		}

		return $data;
	}

	/**
	 * Set the last metadata sync
	 *
	 * @param $data
	 *
	 * @return void
	 */
	private function set_last_metadata_sync( $data ) {
		update_option( 'vimeify_last_metadata_sync', $data );
	}

	/**
	 * Return the last status sync
	 * @return array
	 */
	private function get_last_status_sync() {
		$data = get_option( 'vimeify_last_status_sync' );
		if ( ! is_array( $data ) ) {
			$data = [];
		}

		return $data;
	}

	/**
	 * Set the last status sync
	 *
	 * @param $data
	 *
	 * @return void
	 */
	private function set_last_status_sync( $data ) {
		if ( is_null( $data ) || empty( $data ) ) {
			delete_option( 'vimeify_last_status_sync' );
		} else {
			update_option( 'vimeify_last_status_sync', $data );
		}
	}

	/**
	 * Makes a lock
	 *
	 * @param $type
	 * @param $expiry
	 *
	 * @return void
	 */
	public function set_lock( $type, $expiry = MINUTE_IN_SECONDS * 2 ) {
		set_transient( 'vimeify_' . $type . '_lock', 1, $expiry );
	}

	/**
	 * Frees a lock
	 *
	 * @param $type
	 *
	 * @return void
	 */
	public function delete_lock( $type ) {
		delete_transient( 'vimeify_' . $type . '_lock' );
	}

	/**
	 * Check if process is locked
	 *
	 * @param $type
	 *
	 * @return bool
	 */
	public function check_lock( $type ) {
		return 1 === (int) get_transient( 'vimeify_' . $type . '_lock' );
	}

	/**
	 * Create a new video post from Vimeo API data
	 *
	 * @param array $vimeo_video Video data from Vimeo API
	 *
	 * @return int|false Post ID on success, false on failure
	 */
	private function create_video_from_vimeo( $vimeo_video ) {
		$title       = isset( $vimeo_video['name'] ) ? $vimeo_video['name'] : 'Untitled Video';
		$description = isset( $vimeo_video['description'] ) ? $vimeo_video['description'] : '';
		$vimeo_id    = $vimeo_video['uri'];

		// Create the post using the database component
		$post_id = $this->plugin->system()->database()->create_local_video( $title, $description, $vimeo_id, 'sync' );

		if ( is_wp_error( $post_id ) || ! $post_id ) {
			return false;
		}

		// Set all video metadata (embed link, pictures, link, dimensions, etc.)
		// This also updates post_date and post_date_gmt from Vimeo's created_time
		$this->plugin->system()->vimeo()->set_local_video_metadata( $post_id, $vimeo_video );

		return $post_id;
	}

}