<?php
declare( strict_types=1 );

namespace FiboFilters\Indexer;

use FiboFilters\Config\Env;
use FiboFilters\Index\Descriptors;
use FiboFilters\Indexer\Filters\Database as DatabaseFilters;
use FiboFilters\Integrations\Multilingual;
use FiboFilters\Struct\QueryContext;
use FiboFilters\Util\TemplateUtil;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Class DescriptorsCache
 */
class DescriptorsCache {
	/** @var BuildInfo */
	private $build_info;

	/** @var Descriptors */
	private $descriptors;

	/** @var Multilingual */
	private $multilingual;

	/** @var DatabaseFilters */
	private $database_filters;

	private string $last_build_id = '';

	public function __construct( BuildInfo $build_info, Descriptors $descriptors, Multilingual $multilingual, DatabaseFilters $database_filters ) {
		$this->build_info       = $build_info;
		$this->descriptors      = $descriptors;
		$this->multilingual     = $multilingual;
		$this->database_filters = $database_filters;

		add_filter( 'fibofilters/config/descriptors_json_url', [ $this, 'change_descriptors_json_url' ], 15 );
		add_filter( 'fibofilters/config/descriptors_prevent_cache', [ $this, 'generate_dynamic_length_string' ], 10 );
	}

	/**
	 * Process descriptors JSON files
	 *
	 * This method is called when:
	 * - the index has been built
	 * - the product has been updated
	 */
	public function process_descriptors_files(): void {
		if ( ! $this->is_descriptors_cache_enabled() ) {
			return;
		}

		$this->write_descriptors();
		$this->delete_old_descriptors();
	}

	/**
	 * Dump descriptors to filesystem as JSON files
	 */
	private function write_descriptors(): void {
		/** @var \WP_Filesystem_Base $wp_filesystem */
		global $wp_filesystem;

		if ( ! function_exists( 'WP_Filesystem' ) ) {
			require_once ABSPATH . '/wp-admin/includes/file.php';
		}

		if ( ! WP_Filesystem() ) {
			return;
		}

		if ( ! is_dir( Env::$cache_dir ) ) {
			$wp_filesystem->mkdir( Env::$cache_dir );
			$wp_filesystem->put_contents( Env::$cache_dir . DIRECTORY_SEPARATOR . 'index.html', '' );
		}

		if ( ! is_dir( Env::$cache_dir ) ) {
			return;
		}

		$htaccess_file = Env::$cache_dir . DIRECTORY_SEPARATOR . '.htaccess';
		if ( ! file_exists( $htaccess_file ) ) {
			$wp_filesystem->put_contents( $htaccess_file, TemplateUtil::generate_htaccess_content() );
		}

		$languages = $this->multilingual->is_multilingual() ? $this->multilingual->get_languages() : [ '' ];

		foreach ( $languages as $language ) {
			if ( ! $this->database_filters->filters_index_exists( 'main', true, $language ) ) {
				continue;
			}
			$descriptors = $this->descriptors->get_all_descriptors( new QueryContext( $language ) );
			$build_id    = $this->build_info->get_info( 'build_id' );
			$filename    = empty( $language ) ? sprintf( 'filters-descriptors-%s.json', $build_id ) : sprintf( 'filters-descriptors-%1$s-%2$s.json', $build_id, $language );
			$file        = Env::$cache_dir . DIRECTORY_SEPARATOR . $filename;
			$wp_filesystem->delete( $file );
			$wp_filesystem->put_contents( $file, wp_json_encode( $descriptors ) );
		}
	}

	/**
	 * Remove JSON files with descriptors older than 24h
	 */
	private function delete_old_descriptors(): void {
		/** @var \WP_Filesystem_Base $wp_filesystem */
		global $wp_filesystem;

		if ( ! function_exists( 'WP_Filesystem' ) ) {
			require_once ABSPATH . '/wp-admin/includes/file.php';
		}

		if ( ! WP_Filesystem() ) {
			return;
		}

		/**
		 * @var int $descriptors_expiration_time Expiration time for descriptors in seconds.
		 */
		$descriptors_expiration_time = (int) apply_filters( 'fibofilters/indexer/descriptors_cache/expiration_time', 2 * DAY_IN_SECONDS );

		$json_files = $wp_filesystem->dirlist( Env::$cache_dir, false );
		if ( is_array( $json_files ) ) {
			foreach ( $json_files as $json_file ) {
				if ( strpos( $json_file['name'], '.json' ) === false ) {
					continue;
				}
				if ( empty( $json_file['lastmodunix'] ) ) {
					continue;
				}
				if ( time() - $json_file['lastmodunix'] > $descriptors_expiration_time ) {
					$wp_filesystem->delete( Env::$cache_dir . DIRECTORY_SEPARATOR . $json_file['name'] );
				}
			}
		}
	}

	/**
	 * Change the descriptors JSON URL
	 *
	 * @param string $url The URL of the descriptor's JSON URL.
	 *
	 * @return string
	 */
	public function change_descriptors_json_url( string $url ): string {
		if ( ! $this->is_descriptors_cache_enabled() ) {
			return $url;
		}
		$language = $this->multilingual->is_multilingual() ? $this->multilingual->get_current_language() : '';
		$build_id = $this->build_info->get_info( 'build_id' );
		$filename = empty( $language ) ? sprintf( 'filters-descriptors-%s.json', $build_id ) : sprintf( 'filters-descriptors-%1$s-%2$s.json', $build_id, $language );

		if ( $this->does_descriptor_file_exist( $filename ) ) {
			// Force HTTPS because of cases with mixed content.
			$cache_url = Env::$cache_url;
			if ( strpos( $cache_url, 'http://' ) !== false && is_ssl() ) {
				$cache_url = str_replace( 'http://', 'https://', $cache_url );
			}
			$url                 = $cache_url . '/' . $filename;
			$this->last_build_id = $build_id;

			// If the file is smaller than 50KB, we force refetching all descriptors after product updates.
			if ( $this->get_descriptor_file_size( $filename ) < 50 * 1024 ) {
				add_filter( 'fibofilters/config/refetch_descriptors_after_update', '__return_true' );
			}
		}

		return $url;
	}

	/**
	 * Generate dynamic length string that will be printed in the HTML as a part of fiboFiltersData.
	 * The length of this string is generated based in information stored in a build ID. The two first
	 * digits are information about length. In FiboFilters\Indexer\BuildInfo::generate_build_id_with_length_info(),
	 * there is generated information about length which is different that in the previous build ID.
	 *
	 * This operation should prevent caching HTML in cases, when HTML is different, but the length of all HTML document is the same.
	 * After generating new build ID, the length ot the HTML still could be the same and some caches systems might keep the cache.
	 * From now on after rebuilding the index, length of fiboFiltersData is always different.
	 *
	 * @param string $dynamic_length_string
	 *
	 * @return string
	 */
	public function generate_dynamic_length_string( string $dynamic_length_string ): string {
		if ( ! empty( $this->last_build_id ) ) {
			$length_info = substr( $this->last_build_id, 0, 2 );
			if ( is_numeric( $length_info ) ) {
				$dynamic_length_string = $length_info;
				for ( $i = 2; $i < $length_info; $i++ ) {
					$dynamic_length_string .= 0;
				}
			}
		}

		return $dynamic_length_string;
	}

	public function is_descriptors_cache_enabled(): bool {
		return apply_filters( 'fibofilters/indexer/descriptors_cache/enabled', true );
	}

	private function does_descriptor_file_exist( string $filename ): bool {
		return file_exists( Env::$cache_dir . DIRECTORY_SEPARATOR . $filename );
	}

	private function get_descriptor_file_size( string $filename ): int {
		return (int) filesize( Env::$cache_dir . DIRECTORY_SEPARATOR . $filename );
	}
}
