<?php
/**
 * BricksExtras Media Handler
 *
 * Handles media-related functionality including waveform generation and caching.
 *
 * @package BricksExtras
 */

namespace BricksExtras;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Class for handling media operations like waveform data management.
 */
class BricksExtrasMedia {

	/**
	 * Initialize the waveform handler.
	 *
	 * @return void
	 */
	public function init_waveform_handler() {
		add_action( 'wp_ajax_save_waveform_data', array( $this, 'ajax_save_waveform_data' ) );
		add_action( 'wp_ajax_get_waveform_data', array( $this, 'ajax_get_waveform_data' ) );
		add_action( 'wp_ajax_nopriv_get_waveform_data', array( $this, 'ajax_get_waveform_data' ) );
	}

	/**
	 * Save waveform data to a JSON file.
	 *
	 * @param string $audio_url URL of the audio file.
	 * @param array  $waveform_data Array of waveform peaks.
	 * @param float  $full_duration Optional full duration of audio.
	 * @return array Response with success/error status and message.
	 */
	private static function save_waveform_data( $audio_url, $waveform_data, $full_duration = null ) {

		// Validate inputs.
		if ( empty( $audio_url ) || empty( $waveform_data ) ) {
			return array(
				'success' => false,
				'message' => 'Missing required data',
			);
		}

		// Validate waveform data structure.
		if ( ! self::is_valid_waveform_data( $waveform_data ) ) {
			return array(
				'success' => false,
				'message' => 'Invalid waveform data structure',
			);
		}

		// Strip URL fragments (anything after #) to ensure same audio files share waveform data.
		$clean_audio_url = strtok( $audio_url, '#' );

		// Create a unique filename based on the URL only (not player ID).
		// This allows sharing waveform data between players with the same audio.
		$url_hash = md5( $clean_audio_url );
		$filename = sanitize_file_name( $url_hash . '.json' );

		// Get the upload directory.
		$upload_dir   = wp_upload_dir();
		$waveform_dir = $upload_dir['basedir'] . '/bricksextras/waveforms';

		// Create directory if it doesn't exist.
		if ( ! file_exists( $waveform_dir ) ) {
			wp_mkdir_p( $waveform_dir );
		}

		if ( ! file_exists( $waveform_dir . '/index.php' ) ) {
			file_put_contents( $waveform_dir . '/index.php', '<?php // Silence is golden' );
		}

		// Create the data to save.
		$data_to_save = array(
			'waveform_data' => $waveform_data, // Full audio waveform data.
			'full_duration' => $full_duration,
			'timestamp'     => time(),
		);

		// Save the file.
		$file_path = $waveform_dir . '/' . $filename;

		$json_data = wp_json_encode( $data_to_save );

		// Check file size limit (50KB).
		$max_size = 51200; // 50KB in bytes.
		if ( strlen( $json_data ) > $max_size ) {
			return array(
				'success' => false,
				'message' => 'Waveform data exceeds maximum allowed size (50KB)',
			);
		}

		$saved = file_put_contents( $file_path, $json_data );

		if ( $saved ) {
			return array(
				'success'   => true,
				'message'   => 'Waveform data saved successfully',
				'file'      => $filename,
				'url'       => $upload_dir['baseurl'] . '/bricksextras/waveforms/' . $filename,
				'file_size' => strlen( $json_data ),
			);
		} else {
			return array(
				'success' => false,
				'message' => 'Failed to save waveform data',
			);
		}
	}

	/**
	 * Validate waveform data structure.
	 *
	 * @param array $data The waveform data to validate.
	 * @return bool True if data is valid, false otherwise.
	 */
	public static function is_valid_waveform_data( $data ) {
		// Check if data is an array.
		if ( ! is_array( $data ) ) {
			return false;
		}

		$allowed_keys = array( 'scale', 'min', 'max' );
		if ( count( array_diff( array_keys( $data ), $allowed_keys ) ) > 0 ) {
			return false;
		}

		// Check required properties.
		if ( ! isset( $data['scale'] ) || ! is_numeric( $data['scale'] ) ) {
			return false;
		}

		if ( ! is_int( $data['scale'] ) || $data['scale'] < 1 || $data['scale'] > 10000 ) {
			return false;
		}

		// Check min/max arrays.
		if ( ! isset( $data['min'] ) || ! isset( $data['max'] ) || ! is_array( $data['min'] ) || ! is_array( $data['max'] ) ) {
			return false;
		}

		// Check that arrays have same length and are not empty.
		if ( empty( $data['min'] ) || count( $data['min'] ) !== count( $data['max'] ) ) {
			return false;
		}

		if ( count( $data['min'] ) > 3000 || count( $data['max'] ) > 3000 ) {
			return false;
		}

		foreach ( $data['min'] as $val ) {
			if ( ! is_int( $val ) || $val < -1000 || $val > 1000 ) {
				return false;
			}
		}

		foreach ( $data['max'] as $val ) {
			if ( ! is_int( $val ) || $val < -1000 || $val > 1000 ) {
				return false;
			}
		}

		return true;
	}

	/**
	 * Get waveform data from cache file.
	 *
	 * @param string $audio_url URL of the audio file.
	 * @return array Waveform data or error message.
	 */
	private static function get_waveform_data( $audio_url ) {
		// Validate input.
		if ( empty( $audio_url ) ) {
			return array(
				'success' => false,
				'message' => 'Missing audio URL',
			);
		}

		// Strip URL fragments (anything after #) to ensure same audio files share waveform data.
		$clean_audio_url = strtok( $audio_url, '#' );

		// Create the filename based on the URL only (not player ID).
		// This allows sharing waveform data between players with the same audio.
		$url_hash = md5( $clean_audio_url );
		$filename = $url_hash . '.json';

		// Get the upload directory.
		$upload_dir = wp_upload_dir();
		$file_path  = $upload_dir['basedir'] . '/bricksextras/waveforms/' . $filename;

		// Check if file exists.
		if ( file_exists( $file_path ) ) {
			$file_size = filesize( $file_path );
			if ( false === $file_size || $file_size > 51200 ) {
				return array(
					'success' => false,
					'message' => 'Waveform data not found',
				);
			}

			$file_content  = file_get_contents( $file_path );
			$waveform_data = json_decode( $file_content, true );

			// Validate the waveform data structure.
			if ( null === $waveform_data ) {
				return array(
					'success' => false,
					'message' => 'Invalid JSON format in waveform data file',
				);
			}

			// Check if we have the waveform_data property and validate it.
			if ( ! isset( $waveform_data['waveform_data'] ) || ! self::is_valid_waveform_data( $waveform_data['waveform_data'] ) ) {
				return array(
					'success' => false,
					'message' => 'Invalid waveform data structure',
				);
			}

			return array(
				'success' => true,
				'data'    => $waveform_data,
			);
		} else {
			return array(
				'success' => false,
				'message' => 'Waveform data not found',
			);
		}
	}



	/**
	 * AJAX handler for saving waveform data.
	 *
	 * @return void
	 */
	public static function ajax_save_waveform_data() {
		// Verify nonce for security.
		check_ajax_referer( 'media-nonce', 'security' );

		if ( ! current_user_can( 'edit_posts' ) ) {
			wp_send_json_error( 'Authentication required' );
		}

		$user_id        = get_current_user_id();
		$rate_limit_key = 'bricksextras_waveform_rate_limit_' . $user_id;
		$current_count  = get_transient( $rate_limit_key );

		if ( false === $current_count ) {
			set_transient( $rate_limit_key, 1, 60 );
		} elseif ( $current_count >= 20 ) {
			wp_die();
		} else {
			set_transient( $rate_limit_key, $current_count + 1, 60 );
		}

		$audio_url = isset( $_POST['audio_url'] ) ? esc_url_raw( wp_unslash( $_POST['audio_url'] ) ) : '';
		if ( empty( $audio_url ) ) {
			wp_send_json_error( 'Missing audio URL' );
		}

		// Get additional data from POST.
		$full_duration = Helpers::get_post_value( 'full_duration' ) ? floatval( Helpers::get_post_value( 'full_duration' ) ) : null;

		if ( null !== $full_duration && ( ! is_finite( $full_duration ) || $full_duration < 0.001 || $full_duration > 86400 ) ) {
			return;
		}

		$waveform_data = Helpers::get_post_value( 'waveform_data' );

		if ( empty( $waveform_data ) || '{' !== substr( $waveform_data, 0, 1 ) || '}' !== substr( $waveform_data, -1 ) ) {
			wp_send_json_error( 'Invalid waveform data format' );
			return;
		}

		// Decode the JSON string to get the waveform peaks array.
		$waveform_peaks = null;
		if ( ! empty( $waveform_data ) ) {
			$waveform_peaks = json_decode( $waveform_data, true );

			// Check if JSON decode was successful.
			if ( null === $waveform_peaks ) {
				wp_send_json_error( 'Invalid JSON format in waveform data' );
				return;
			}

			// Validate the waveform data structure.
			if ( ! self::is_valid_waveform_data( $waveform_peaks ) ) {
				wp_send_json_error( 'Invalid waveform data structure' );
				return;
			}
		} else {
			wp_send_json_error( 'Empty waveform data' );
			return;
		}

		// Save the file.
		$result = self::save_waveform_data( $audio_url, $waveform_peaks, $full_duration );

		if ( $result['success'] ) {
			wp_send_json_success( $result );
		} else {
			wp_send_json_error( $result['message'] );
		}
	}

	/**
	 * AJAX handler for retrieving waveform data.
	 *
	 * @return void
	 */
	public static function ajax_get_waveform_data() {
		// Verify nonce for security.
		check_ajax_referer( 'media-nonce', 'security' );

		$audio_url = isset( $_GET['audio_url'] ) ? esc_url_raw( wp_unslash( $_GET['audio_url'] ) ) : '';

		$result = self::get_waveform_data( $audio_url );

		if ( $result['success'] ) {
			wp_send_json_success( $result['data'] );
		} else {
			wp_send_json_error( $result['message'] );
		}
	}

	/**
	 * Get or create cached poster for YouTube/Vimeo videos.
	 * Checks cache first, fetches if needed.
	 *
	 * @param string $video_url The video URL (YouTube or Vimeo).
	 * @return array|false Array with 'url', 'width', 'height', 'provider', 'video_id' or false on failure.
	 */
	public static function get_video_poster( $video_url ) {
		if ( empty( $video_url ) ) {
			return false;
		}

		// Parse the video URL to get provider and ID.
		$video_info = self::parse_video_url( $video_url );
		if ( ! $video_info ) {
			return false;
		}

		$video_id = $video_info['video_id'];
		$hash     = $video_info['hash'] ?? null; // For unlisted Vimeo videos.

		// Validate video_id.
		if ( ! preg_match( '/^[a-zA-Z0-9_-]{6,20}$/', $video_id ) ) {
			return false;
		}

		if ( null !== $hash && ! preg_match( '/^[a-zA-Z0-9]{6,20}$/', $hash ) ) {
			return false;
		}

		$provider = ( 'youtube' === $video_info['provider'] ) ? 'youtube' : 'vimeo';

		// Get upload directory.
		$upload_dir = wp_upload_dir();
		if ( ! empty( $upload_dir['error'] ) ) {
			return false;
		}

		$cache_base_dir = $upload_dir['basedir'] . '/bricksextras/posters';
		$cache_base_url = $upload_dir['baseurl'] . '/bricksextras/posters';

		// Video-specific directory.
		$video_dir  = $cache_base_dir . '/' . $video_id;
		$cache_file = $video_dir . '/poster-cache.json';

		// Check if we have cached data.
		if ( file_exists( $cache_file ) ) {
			$cache_filesize = filesize( $cache_file );
			if ( false === $cache_filesize || $cache_filesize > 10240 ) {
				wp_delete_file( $cache_file );
			} else {
				$cached_data = json_decode( file_get_contents( $cache_file ), true, 3 );

				// Check for JSON errors.
				if ( JSON_ERROR_NONE !== json_last_error() ) {
					wp_delete_file( $cache_file );
				} elseif ( is_array( $cached_data ) && isset( $cached_data['poster_file'] ) ) {
					$poster_file = $cached_data['poster_file'];

					if ( ! preg_match( '/^poster\.(jpg|jpeg|png|webp)$/', $poster_file ) ) {
						return false;
					}

					$poster_path = $video_dir . '/' . $poster_file;
					if ( file_exists( $poster_path ) ) {

						// Return cached poster URL.
						return array(
							'url'      => esc_url( $cache_base_url . '/' . $video_id . '/' . $poster_file ),
							'provider' => $provider,
						);
					}
				}
			}
		}

		// Not cached or invalid cache - fetch from provider.
		$result = self::fetch_and_cache_poster( $provider, $video_id, $hash );

		return $result;
	}

	/**
	 * Fetch poster from provider and cache it locally.
	 *
	 * @param string      $provider 'youtube' or 'vimeo'.
	 * @param string      $video_id The video ID.
	 * @param string|null $hash Optional hash for unlisted Vimeo videos.
	 * @return array|false Array with poster info or false on failure.
	 */
	private static function fetch_and_cache_poster( $provider, $video_id, $hash = null ) {

		// 6-20 alphanumeric characters, underscores, and hyphens.
		if ( ! preg_match( '/^[a-zA-Z0-9_-]{6,20}$/', $video_id ) ) {
			return false;
		}

		$rate_limit_key = 'bricksextras_poster_fetch_' . md5( $video_id );
		$fetch_count    = get_transient( $rate_limit_key );

		if ( false !== $fetch_count && $fetch_count >= 10 ) {
			return false;
		}

		set_transient( $rate_limit_key, ( false === $fetch_count ? 1 : $fetch_count + 1 ), 60 );

		// Get upload directory.
		$upload_dir = wp_upload_dir();
		if ( ! empty( $upload_dir['error'] ) ) {
			return false;
		}

		$cache_base_dir = $upload_dir['basedir'] . '/bricksextras/posters';
		$cache_base_url = $upload_dir['baseurl'] . '/bricksextras/posters';

		// Create base directory if needed.
		if ( ! file_exists( $cache_base_dir ) ) {
			if ( ! wp_mkdir_p( $cache_base_dir ) ) {
				return false;
			}
			file_put_contents( $cache_base_dir . '/index.php', '<?php // Silence is golden' );
		}

		// Create video-specific directory.
		$video_dir = $cache_base_dir . '/' . $video_id;
		if ( ! file_exists( $video_dir ) ) {
			if ( ! wp_mkdir_p( $video_dir ) ) {
				return false;
			}
			file_put_contents( $video_dir . '/index.php', '<?php // Silence is golden' );
		}

		$poster_url = null;
		$extension  = 'jpg';

		// Fetch poster URL based on provider.
		if ( 'youtube' === $provider ) {

			$sizes = array(
				'maxresdefault' => array(
					'width'  => 1280,
					'height' => 720,
				),
				'sddefault'     => array(
					'width'  => 640,
					'height' => 480,
				),
				'hqdefault'     => array(
					'width'  => 480,
					'height' => 360,
				),
			);

			foreach ( $sizes as $size => $dimensions ) {
				foreach ( array( 'webp', 'jpg' ) as $format ) {
					$path_prefix = ( 'webp' === $format ) ? 'vi_webp' : 'vi';
					$test_url    = "https://i.ytimg.com/{$path_prefix}/{$video_id}/{$size}.{$format}";

					// Test if this URL is available.
					$test_response = wp_safe_remote_head( $test_url, array( 'timeout' => 10 ) );

					if ( ! is_wp_error( $test_response ) && 200 === wp_remote_retrieve_response_code( $test_response ) ) {
						$poster_url = $test_url;
						$extension  = $format;
						break 2; // Break out of both loops.
					}
				}
			}

			// If no poster found, return false.
			if ( ! $poster_url ) {
				return false;
			}
		} elseif ( 'vimeo' === $provider ) {
			// Use oEmbed API to get thumbnail.
			$oembed_url = "https://vimeo.com/api/oembed.json?url=https://vimeo.com/{$video_id}";
			if ( $hash ) {
				$oembed_url .= "/{$hash}";
			}

			$response = wp_safe_remote_get( $oembed_url, array( 'timeout' => 10 ) );

			if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
				return false;
			}

			$data = json_decode( wp_remote_retrieve_body( $response ), true );

			if ( empty( $data['thumbnail_url'] ) ) {
				return false;
			}

			// Parse thumbnail ID from Vimeo CDN.
			if ( preg_match( '/^https:\/\/i\.vimeocdn\.com\/video\/([0-9]+-[a-zA-Z0-9]+(?:-d)?)_/', $data['thumbnail_url'], $matches ) ) {
				$thumbnail_id = $matches[1];

				// Get high-res webp version.
				$poster_url = "https://i.vimeocdn.com/video/{$thumbnail_id}_1920x1080.webp";
				$extension  = 'webp';
			}
		} else {
			return false;
		}

		if ( ! $poster_url ) {
			return false;
		}

		// Download the poster.
		$poster_file = 'poster.' . $extension;
		$poster_path = $video_dir . '/' . $poster_file;

		$response = wp_safe_remote_get(
			$poster_url,
			array(
				'timeout'  => 10,
				'stream'   => true,
				'filename' => $poster_path,
			)
		);

		if ( 200 !== wp_remote_retrieve_response_code( $response ) || is_wp_error( $response ) ) {
			return false;
		}

		// Verify the downloaded file.
		if ( ! file_exists( $poster_path ) ) {
			return false;
		}

		// Check file size (max 1MB).
		$filesize = filesize( $poster_path );
		if ( false === $filesize || $filesize > 1 * 1024 * 1024 ) {
			wp_delete_file( $poster_path );
			return false;
		}

		// Get actual image dimensions.
		$image_info = wp_getimagesize( $poster_path );
		if ( ! $image_info ) {
			wp_delete_file( $poster_path );
			return false;
		}

		// Validate MIME type.
		$allowed_mime_types = array( 'image/jpeg', 'image/png', 'image/webp' );
		if ( ! in_array( $image_info['mime'], $allowed_mime_types, true ) ) {
			wp_delete_file( $poster_path );
			return false;
		}

		// Save cache data.
		$cache_data = array(
			'provider'    => $provider,
			'poster_file' => $poster_file,
		);

		// Encode JSON with error checking.
		$json_data = wp_json_encode( $cache_data, JSON_PRETTY_PRINT );
		if ( false === $json_data ) {
			wp_delete_file( $poster_path );
			return false;
		}

		// write to temp file, then rename.
		$temp_file     = $video_dir . '/poster-cache.tmp';
		$bytes_written = file_put_contents( $temp_file, $json_data );

		if ( false === $bytes_written ) {
			wp_delete_file( $temp_file );
			wp_delete_file( $poster_path );
			return false;
		}

		// rename.
		if ( ! rename( $temp_file, $video_dir . '/poster-cache.json' ) ) {
			wp_delete_file( $temp_file );
			wp_delete_file( $poster_path );
			return false;
		}

		return array(
			'url'      => esc_url( $cache_base_url . '/' . $video_id . '/' . $poster_file ),
			'provider' => $provider,
		);
	}

	/**
	 * Parse video URL to extract provider and video ID.
	 * Supports YouTube and Vimeo URL formats.
	 *
	 * @param string $url The video URL.
	 * @return array|false Array with 'provider', 'video_id', and optional 'hash' or false.
	 */
	private static function parse_video_url( $url ) {

		// YouTube patterns (supports all Vidstack formats).
		$youtube_patterns = array(
			'/^(?:https?:\/\/)?youtube\/([a-zA-Z0-9_-]{6,20})$/',  // youtube/_cMxraX_5RE or http://youtube/_cMxraX_5RE.
			'/(?:youtube\.com|youtube-nocookie\.com)\/watch\?v=([a-zA-Z0-9_-]{6,20})/',  // watch?v= (both domains).
			'/youtu\.be\/([a-zA-Z0-9_-]{6,20})/',  // youtu.be short links.
			'/(?:youtube\.com|youtube-nocookie\.com)\/embed\/([a-zA-Z0-9_-]{6,20})/',  // embed (both domains).
			'/(?:youtube\.com|youtube-nocookie\.com)\/shorts\/([a-zA-Z0-9_-]{6,20})/',  // shorts (both domains).
		);

		foreach ( $youtube_patterns as $pattern ) {
			if ( preg_match( $pattern, $url, $matches ) ) {
				return array(
					'provider' => 'youtube',
					'video_id' => $matches[1],
				);
			}
		}

		// Vimeo patterns (supports all Vidstack formats).
		$vimeo_patterns = array(
			'/^(?:https?:\/\/)?vimeo\/([0-9]{6,20})$/',  // vimeo/640499893 or http://vimeo/640499893.
			'/(?:vimeo\.com\/|player\.vimeo\.com\/video\/)([0-9]{6,20})(?:\/([a-zA-Z0-9]{6,20}))?/',  // URLs with optional hash.
		);

		foreach ( $vimeo_patterns as $pattern ) {
			if ( preg_match( $pattern, $url, $matches ) ) {
				$result = array(
					'provider' => 'vimeo',
					'video_id' => $matches[1],
				);

				// Check for hash (unlisted videos).
				if ( isset( $matches[2] ) ) {
					$result['hash'] = $matches[2];
				}

				return $result;
			}
		}

		return false;
	}
}
