<?php
namespace Prodigy\Includes;

use Prodigy\admin\Prodigy_Admin_Setup_Wizard;
use Predis\Client;
use Predis\Connection\ConnectionException;

defined( 'ABSPATH' ) || exit;

/**
 * Class Prodigy_Cache
 *
 * @package Prodigy\Includes
 * @version 2.0.6
 */
class Prodigy_Cache {

	const PLUGIN_TAG                    = 'prodigy-commerce';
	const PRODUCT_TAG                   = ':prodigy_product:';
	const CATALOG_FILTERS_TAG           = ':prodigy_catalog_filters:';
	const ORDER_TAG                     = ':prodigy_order:';
	const RELATED_PRODUCT_TAG           = ':prodigy_related_products:';
	const PRODUCTS_SHORTCODE_TAG        = ':prodigy_products_shortcode:';
	const CATEGORIES_SHORTCODE_TAG      = ':prodigy_categories_shortcode:';
	const BRANDS_SHORTCODE_TAG          = ':prodigy_brands_shortcode:';
	const BRANDS_LIST_TAG               = ':prodigy_brands_list:';
	const SETTINGS_TAG                  = ':prodigy_hosted_system_settings:';
	const USER_INFO_TAG                 = ':prodigy_user_info';
	const DEFAULT_EXPIRATION_TIME       = 900;
	const EXPIRATION_TIME_OPTION        = 'pg_cache_expiration_time';
	const CUSTOM_EXPIRATION_TIME_OPTION = 'pg_custom_expiration_cache_time';
	const CACHE_STATE_OPTION            = 'pg_enable_cache';
	const DEFAULT_CACHE_STATE           = 1;
	const REDIS_HOST_OPTION             = 'pg_redis_host';
	const REDIS_PORT_OPTION             = 'pg_redis_port';
	const REDIS_PASSWORD_OPTION         = 'pg_redis_password';
	const REDIS_DATABASE_OPTION         = 'pg_redis_database';
	const DEFAULT_REDIS_HOST            = '127.0.0.1';
	const DEFAULT_REDIS_PORT            = 6379;
	const DEFAULT_REDIS_DATABASE        = 0;

	/**
	 * @var string
	 */
	public string $prefix;

	public $is_enabled;

	/**
	 * @var Client|null
	 */
	private $redis_client = null;

	/**
	 * @var bool
	 */
	private $redis_available = false;

	/**
	 * Prodigy_Cache constructor.
	 */
	public function __construct() {
		$this->is_enabled = get_option( self::CACHE_STATE_OPTION );
		$this->prefix     = $this->generate_prefix();
		$this->init_redis();
	}

	/**
	 * Initialize Redis connection
	 *
	 * @return void
	 */
	private function init_redis(): void {
		if ( ! class_exists( 'Predis\Client' ) ) {
			$this->redis_available = false;
			return;
		}

		try {
			$host     = get_option( self::REDIS_HOST_OPTION, self::DEFAULT_REDIS_HOST );
			$port     = (int) get_option( self::REDIS_PORT_OPTION, self::DEFAULT_REDIS_PORT );
			$password = get_option( self::REDIS_PASSWORD_OPTION, null );
			$database = (int) get_option( self::REDIS_DATABASE_OPTION, self::DEFAULT_REDIS_DATABASE );

			$parameters = array(
				'host'     => $host,
				'port'     => $port,
				'database' => $database,
			);

			if ( ! empty( $password ) ) {
				$parameters['password'] = $password;
			}

			$this->redis_client = new Client( $parameters );
			// Test connection with ping (lazy connection in Predis 2.0)
			$this->redis_client->ping();
			$this->redis_available = true;
		} catch ( ConnectionException $e ) {
			$this->redis_available = false;
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'Prodigy Cache: Redis connection failed - ' . $e->getMessage() );
			}
		} catch ( \Exception $e ) {
			$this->redis_available = false;
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'Prodigy Cache: Redis initialization error - ' . $e->getMessage() );
			}
		}
	}

	/**
	 * Get Redis client instance
	 *
	 * @return Client|null
	 */
	private function get_redis(): ?Client {
		if ( ! $this->redis_available || null === $this->redis_client ) {
			return null;
		}

		try {
			$this->redis_client->ping();
			return $this->redis_client;
		} catch ( \Exception $e ) {
			$this->redis_available = false;
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'Prodigy Cache: Redis ping failed - ' . $e->getMessage() );
			}
			return null;
		}
	}

	/**
	 * @return string
	 */
	private function generate_prefix(): string {
		$access_cookie = isset( $_COOKIE[ Prodigy_User::ACCESS_COOKIE_NAME ] ) ? sanitize_text_field( wp_unslash( $_COOKIE[ Prodigy_User::ACCESS_COOKIE_NAME ] ) ) : '';
		$hash          = Prodigy_User::is_logged_in() ? $access_cookie : get_option( Prodigy_Admin_Setup_Wizard::OPTION_STORE_KEY );

		return md5( self::PLUGIN_TAG . $hash );
	}

	/**
	 * @param string $key
	 *
	 * @return string
	 */
	private function get_product_key( string $key ): string {
		return $this->prefix . self::PRODUCT_TAG . $key;
	}

	/**
	 * @param string $id
	 *
	 * @return string
	 */
	private function get_order_key( string $id ): string {
		return $this->prefix . self::ORDER_TAG . $id;
	}

	/**
	 * @param string $id
	 *
	 * @return string
	 */
	private function get_related_product_key( string $id ): string {
		return $this->prefix . self::RELATED_PRODUCT_TAG . md5( $id );
	}

	/**
	 * @param string $filter
	 *
	 * @return string
	 */
	private function get_catalog_filters_key( string $filter = '' ): string {
		return $this->get_catalog_filters_prefix() . md5( $filter );
	}

	/**
	 * @param string $filter
	 *
	 * @return string
	 */
	private function get_product_shortcode_key( string $filter = '' ): string {
		return $this->get_products_shortcode_prefix() . md5( $filter );
	}

	/**
	 * @param string $filter
	 *
	 * @return string
	 */
	private function get_categories_shortcode_key( string $filter = '' ): string {
		return $this->get_categories_shortcode_prefix() . md5( $filter );
	}

	/**
	 *
	 * @return string
	 */
	private function get_brands_shortcode_key(): string {
		return $this->get_brands_shortcode_prefix() . md5( 'brands' );
	}

	/**
	 * @param int $id_remote_product
	 *
	 * @return string
	 */
	private function get_brands_list_key( int $id_remote_product ): string {
		return $this->get_brands_list_prefix() . $id_remote_product;
	}

	/**
	 * @return string
	 */
	private function get_settings_key(): string {
		$current_domain = get_option( Prodigy_Admin_Setup_Wizard::OPTION_DOMAIN_HOSTED_SYSTEM );
		return $this->prefix . self::SETTINGS_TAG . $current_domain;
	}

	/**
	 * @param string $key
	 *
	 * @return string
	 */
	private function get_user_info_key( string $key ): string {
		return $this->prefix . self::USER_INFO_TAG . $key;
	}

	/**
	 * @return string
	 */
	private function get_categories_shortcode_prefix(): string {
		return $this->prefix . self::CATEGORIES_SHORTCODE_TAG;
	}

	/**
	 * @return string
	 */
	private function get_brands_shortcode_prefix(): string {
		return $this->prefix . self::BRANDS_SHORTCODE_TAG;
	}

	/**
	 * @return string
	 */
	private function get_brands_list_prefix(): string {
		return $this->prefix . self::BRANDS_LIST_TAG;
	}

	/**
	 * @return string
	 */
	private function get_products_shortcode_prefix(): string {
		return $this->prefix . self::PRODUCTS_SHORTCODE_TAG;
	}

	/**
	 * @return string
	 */
	private function get_catalog_filters_prefix(): string {
		return $this->prefix . self::CATALOG_FILTERS_TAG;
	}

	/**
	 * @param string $key
	 *
	 * @return mixed
	 */
	public function get_product( string $key ) {
		return $this->get_cache_value( $this->get_product_key( $key ) );
	}

	/**
	 * @param string $id
	 *
	 * @return mixed
	 */
	public function get_related_product( string $id ) {
		return $this->get_cache_value( $this->get_related_product_key( $id ) );
	}

	/**
	 * @param string $id
	 *
	 * @return mixed
	 */
	public function get_order( string $id ) {
		return $this->get_cache_value( $this->get_order_key( $id ) );
	}

	/**
	 *
	 * @return int
	 */
	private function set_expiration_time(): int {
		if ( $this->is_enabled ) {
			$expiration_time = get_option( self::EXPIRATION_TIME_OPTION );
			$custom_time     = get_option( self::CUSTOM_EXPIRATION_TIME_OPTION );

			return (int) ( $expiration_time === 'custom' ? $custom_time : $expiration_time );
		}

		return 0;
	}

	/**
	 * Get value from cache (Redis or fallback to transients)
	 *
	 * @param string $key Cache key.
	 * @return mixed
	 */
	private function get_cache_value( string $key ) {
		if ( ! $this->is_enabled ) {
			return false;
		}

		$redis = $this->get_redis();
		if ( $redis ) {
			try {
				$value = $redis->get( $key );
				if ( null !== $value && false !== $value ) {
					$decoded = json_decode( $value, true );
					if ( json_last_error() === JSON_ERROR_NONE ) {
						return $decoded;
					}
					// If JSON decode fails, try to return as string (for backward compatibility)
					return $value;
				}
				return false;
			} catch ( \Exception $e ) {
				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					error_log( 'Prodigy Cache: Redis get error - ' . $e->getMessage() );
				}
			}
		}

		return get_transient( $key );
	}

	/**
	 * Set value in cache (Redis or fallback to transients)
	 *
	 * @param string $key Cache key.
	 * @param mixed  $data Data to cache.
	 * @param int    $time Expiration time in seconds.
	 * @return bool
	 */
	private function set_cache_value( string $key, $data, int $time ): bool {
		if ( ! $this->is_enabled ) {
			return false;
		}

		$redis = $this->get_redis();
		if ( $redis ) {
			try {
				$serialized = json_encode( $data, JSON_UNESCAPED_UNICODE );
				if ( false === $serialized ) {
					// If JSON encoding fails, fallback to transients
					if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
						error_log( 'Prodigy Cache: JSON encode error - ' . json_last_error_msg() );
					}
					return set_transient( $key, $data, $time );
				}

				if ( $time > 0 ) {
					$redis->setex( $key, $time, $serialized );
				} else {
					$redis->set( $key, $serialized );
				}
				return true;
			} catch ( \Exception $e ) {
				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					error_log( 'Prodigy Cache: Redis set error - ' . $e->getMessage() );
				}
			}
		}

		// Fallback to WordPress transients
		return set_transient( $key, $data, $time );
	}

	/**
	 * Delete value from cache (Redis or fallback to transients)
	 *
	 * @param string $key Cache key.
	 * @return bool
	 */
	private function delete_cache_value( string $key ): bool {
		$redis = $this->get_redis();
		if ( $redis ) {
			try {
				$redis->del( $key );
				return true;
			} catch ( \Exception $e ) {
				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					error_log( 'Prodigy Cache: Redis delete error - ' . $e->getMessage() );
				}
			}
		}

		// Fallback to WordPress transients
		return delete_transient( $key );
	}

	/**
	 * @param string $key
	 * @param mixed  $data
	 * @param int    $time
	 *
	 * @return bool
	 */
	public function set_cache( string $key, $data, int $time ): bool {
		return $this->set_cache_value( $key, $data, $time );
	}

	/**
	 * @param string $key
	 * @param array  $product_data
	 *
	 * @return bool
	 */
	public function set_product( string $key, array $product_data ): bool {
		return $this->set_cache( $this->get_product_key( $key ), $product_data, $this->set_expiration_time() );
	}

	/**
	 * @param string $id
	 * @param array  $data
	 *
	 * @return bool
	 */
	public function set_related_product( string $id, array $data ): bool {
		return $this->set_cache( $this->get_related_product_key( $id ), $data, $this->set_expiration_time() );
	}

	/**
	 * @param string $id
	 * @param array  $data
	 *
	 * @return bool
	 */
	public function set_order( string $id, array $data ): bool {
		return $this->set_cache( $this->get_order_key( $id ), $data, $this->set_expiration_time() );
	}

	/**
	 * @param string $id Order token.
	 *
	 * @return bool
	 */
	public function delete_order( string $id ): bool {
		return $this->delete_cache_value( $this->get_order_key( $id ) );
	}

	/**
	 * @param array  $data
	 * @param string $filter
	 *
	 * @return bool
	 */
	public function set_catalog_filters( array $data, string $filter ): bool {
		return $this->set_cache( $this->get_catalog_filters_key( $filter ), $data, $this->set_expiration_time() );
	}

	/**
	 * @param   string $filter
	 *
	 * @return mixed
	 */
	public function get_catalog_filters( string $filter ) {
		return $this->get_cache_value( $this->get_catalog_filters_key( $filter ) );
	}

	/**
	 * @param array $data
	 *
	 * @return bool
	 */
	public function set_settings( array $data ): bool {
		return $this->set_cache( $this->get_settings_key(), $data, $this->set_expiration_time() );
	}

	/**
	 *
	 * @return mixed
	 */
	public function get_settings() {
		return $this->get_cache_value( $this->get_settings_key() );
	}


	/**
	 * @param string $key
	 *
	 * @return mixed
	 */
	public function get_user_info( string $key ) {
		return $this->get_cache_value( $this->get_user_info_key( $key ) );
	}

	/**
	 * @param array  $data
	 * @param string $key
	 *
	 * @return bool
	 */
	public function set_user_info( array $data, string $key ): bool {
		return $this->set_cache( $this->get_user_info_key( $key ), $data, $this->set_expiration_time() );
	}

	/**
	 * @param array  $data
	 * @param string $filter
	 *
	 * @return bool
	 */
	public function set_products_shortcode( array $data, string $filter ): bool {
		return $this->set_cache( $this->get_product_shortcode_key( $filter ), $data, $this->set_expiration_time() );
	}

	/**
	 * @param string $filter
	 *
	 * @return mixed
	 */
	public function get_products_shortcode( string $filter ) {
		return $this->get_cache_value( $this->get_product_shortcode_key( $filter ) );
	}

	/**
	 * @param array  $data
	 * @param string $filter
	 *
	 * @return bool
	 */
	public function set_categories_shortcode( array $data, string $filter ): bool {
		return $this->set_cache( $this->get_categories_shortcode_key( $filter ), $data, $this->set_expiration_time() );
	}

	/**
	 * @param array $data
	 *
	 * @return bool
	 */
	public function set_brands_shortcode( array $data ): bool {
		return $this->set_cache( $this->get_brands_shortcode_key(), $data, $this->set_expiration_time() );
	}

	/**
	 * @param string $filter
	 *
	 * @return mixed
	 */
	public function get_categories_shortcode( string $filter ) {
		return $this->get_cache_value( $this->get_categories_shortcode_key( $filter ) );
	}

	/**
	 *
	 * @return mixed
	 */
	public function get_brands_shortcode() {
		return $this->get_cache_value( $this->get_brands_shortcode_key() );
	}


	/**
	 * @param array $data
	 * @param int   $id
	 *
	 * @return bool
	 */
	public function set_brands_list( array $data, int $id ): bool {
		return $this->set_cache( $this->get_brands_list_key( $id ), $data, $this->set_expiration_time() );
	}

	/**
	 * @param int $id_remote_product
	 *
	 * @return mixed
	 */
	public function get_brands_list( int $id_remote_product ) {
		return $this->get_cache_value( $this->get_brands_list_key( $id_remote_product ) );
	}

	/**
	 * @return bool
	 */
	public function reset_settings(): bool {
		return $this->delete_cache_value( $this->get_settings_key() );
	}


	/**
	 *
	 * @param string $key
	 *
	 * @return void
	 */
	public function reset_product( string $key ) {
		$this->clear( $this->get_product_key( $key ) );
	}

	/**
	 * @param int $id
	 *
	 * @return bool
	 */
	public function reset_product_brands_list( int $id ): bool {
		return $this->delete_cache_value( $this->get_brands_list_key( $id ) );
	}

	/**
	 * @return void
	 */
	public function reset_catalog() {
		$this->clear( $this->get_catalog_filters_prefix() );
		$this->clear( $this->get_products_shortcode_prefix() );
	}

	/**
	 * @param  string $id
	 *
	 * @return void
	 */
	public function reset_related_products( string $id ) {
		$this->clear( $this->get_related_product_key( $id ) );
	}

	/**
	 * @param  string $prefix
	 *
	 * @return void
	 */
	public function clear( string $prefix = '' ) {
		global $wpdb;

		$redis = $this->get_redis();
		if ( $redis ) {
			try {
				if ( empty( $prefix ) ) {
					$pattern = $this->prefix . '*';
				} else {
					$pattern = $prefix . '*';
				}

				$keys = $redis->keys( $pattern );
				if ( ! empty( $keys ) && is_array( $keys ) ) {
					$redis->del( $keys );
				}
			} catch ( \Exception $e ) {
				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
					error_log( 'Prodigy Cache: Redis clear error - ' . $e->getMessage() );
				}
			}
		}

		if ( empty( $prefix ) ) {
			$prefix = esc_sql( $this->prefix );
			$t      = esc_sql( "_transient_%$prefix%" );
		} else {
			$t = "%_transient_$prefix" . '%';
		}

		$transients = $wpdb->get_col(
			$wpdb->prepare(
				"
			      SELECT option_name
			      FROM {$wpdb->options}
			      WHERE option_name like %s
			    ",
				$t
			)
		);

		foreach ( $transients as $transient ) {
			$key = str_replace( '_transient_', '', $transient );
			delete_transient( $key );
		}

		wp_cache_flush();
	}
}
