<?php

namespace SmartCrawl\Third_Party_Import;

use SmartCrawl\Settings;
use SmartCrawl\Multisite\Subsite_Process_Runner;

abstract class Importer {

	private $status = null;

	abstract public function data_exists();

	abstract protected function get_source_plugins();

	public function get_active_source_plugin() {
		$source_plugin = $this->get_source_plugins();
		foreach ( $source_plugin as $plugin ) {
			if ( is_plugin_active( $plugin ) ) {
				return $plugin;
			}
		}

		return false;
	}

	public function get_deactivation_link() {
		$active_plugin = $this->get_active_source_plugin();
		if ( ! $active_plugin ) {
			return false;
		}

		return wp_nonce_url( 'plugins.php?action=deactivate&amp;plugin=' . $active_plugin . '&amp;plugin_status=all', 'deactivate-plugin_' . $active_plugin );
	}

	public function import_for_all_sites( $options = array() ) {
		$runner          = new Subsite_Process_Runner(
			$this->get_next_network_site_option_id(),
			array( $this, 'import' )
		);
		$total_sites     = $runner->get_total_site_count();
		$processed_sites = $runner->run( $options );
		$this->update_site_status( $total_sites, $processed_sites );
		$this->enable_settings_page_on_subsites();
	}

	public function get_status() {
		return empty( $this->status ) ? array() : $this->status;
	}

	protected function set_status( $status ) {
		$this->status = $status;
	}

	protected function update_status( $status ) {
		$this->status = wp_parse_args( $status, $this->get_status() );
	}

	abstract protected function get_next_network_site_option_id();

	public function import( $options = array() ) {
		$options          = wp_parse_args(
			$options,
			array(
				'import-options'          => true,
				'import-term-meta'        => true,
				'import-post-meta'        => true,
				'force-restart'           => false,
				'keep-existing-post-meta' => false,
			)
		);
		$import_options   = (bool) \smartcrawl_get_array_value( $options, 'import-options' );
		$import_term_meta = (bool) \smartcrawl_get_array_value( $options, 'import-term-meta' );
		$import_post_meta = (bool) \smartcrawl_get_array_value( $options, 'import-post-meta' );
		$force_restart    = (bool) \smartcrawl_get_array_value( $options, 'force-restart' );
		$keep_post_meta   = (bool) \smartcrawl_get_array_value( $options, 'keep-existing-post-meta' );

		if ( ! $this->is_import_in_progress() || $force_restart ) {
			$this->set_import_flag();
			if ( $import_options ) {
				$this->remove_existing_wds_options();
				$this->import_options();
			}
			if ( $import_term_meta ) {
				$this->remove_existing_wds_taxonomy_meta();
				$this->import_taxonomy_meta();
			}
			if ( $import_post_meta && ! $keep_post_meta ) {
				$this->remove_existing_wds_post_meta();
			}
		}

		// If post meta doesn't need to be imported then we're done.
		$complete = $import_post_meta ? $this->import_post_meta() : true;
		if ( $complete ) {
			$this->reset_import_flag();
		}

		return $complete;
	}

	public function is_import_in_progress() {
		return (bool) get_option( $this->get_import_in_progress_option_id() );
	}

	abstract protected function get_import_in_progress_option_id();

	private function set_import_flag() {
		update_option( $this->get_import_in_progress_option_id(), true );
	}

	private function remove_existing_wds_options() {
		Settings::reset_options();

		global $wpdb;

		$wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '%wds-sitemap%'" ); // phpcs:ignore -- direct db call needed.
	}

	private function remove_existing_wds_taxonomy_meta() {
		delete_option( 'wds_taxonomy_meta' );
	}

	abstract public function import_options();

	abstract public function import_taxonomy_meta();

	private function remove_existing_wds_post_meta() {
		global $wpdb;
		$wpdb->query( "DELETE FROM {$wpdb->postmeta} WHERE meta_key LIKE '_wds_%'" ); // phpcs:ignore -- direct db call needed.
	}

	abstract public function import_post_meta();

	private function reset_import_flag() {
		delete_option( $this->get_import_in_progress_option_id() );
	}

	private function enable_settings_page_on_subsites() {
		$blog_tabs                 = get_site_option( 'wds_blog_tabs', array() );
		$blog_tabs['wds_settings'] = true;
		update_site_option( 'wds_blog_tabs', $blog_tabs );
	}

	public function is_network_import_in_progress() {
		return get_site_option( $this->get_next_network_site_option_id(), false ) !== false;
	}

	protected function get_posts_with_source_metas( $prefix ) {
		global $wpdb;
		$posts_with_target_meta = implode( ',', $this->get_posts_with_target_metas() );
		$not_in                 = $posts_with_target_meta ? $posts_with_target_meta : '-1';
		$meta_query             = "SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key LIKE '{$prefix}%' AND post_id NOT IN ({$not_in}) GROUP BY post_id";

		return $wpdb->get_col( $meta_query ); // phpcs:ignore -- Preparation difficult due to % escaping and complex IN clause
	}

	protected function get_posts_with_target_metas() {
		global $wpdb;

		return $wpdb->get_col( "SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key LIKE '_wds_%' AND meta_key NOT IN ('_wds_analysis','_wds_readability') GROUP BY post_id" ); // phpcs:ignore -- direct db call needed.
	}

	protected function load_mapping_file( $file ) {
		return include \SMARTCRAWL_PLUGIN_DIR . 'core/resources/' . $file;
	}

	protected function save_options( $options ) {
		foreach ( $options as $option_key => $values ) {
			remove_all_filters( 'sanitize_option_' . $option_key );
			update_option( $option_key, $values );
		}
	}

	protected function try_custom_handlers( $source_key, $source_value, $target_options ) {
		$custom_handler = null;
		foreach ( $this->expand_mappings( $this->get_custom_handlers() ) as $pattern => $callback ) {
			if ( preg_match( '#' . $pattern . '#', $source_key ) ) {
				$custom_handler = $callback;
			}
		}

		if ( ! $custom_handler ) {
			return $target_options;
		}

		$target_options = call_user_func_array(
			array( $this, $custom_handler ),
			array( $source_key, $source_value, $target_options )
		);

		return $target_options;
	}

	protected function expand_mappings( $mappings ) {
		$post_types = $this->get_post_types();
		$taxonomies = $this->get_taxonomies();

		foreach ( $mappings as $source_key => $target_key ) {
			if ( ! $this->is_custom_type_option( $source_key ) ) {
				continue;
			}

			unset( $mappings[ $source_key ] );

			if ( $this->is_post_type_option( $source_key ) ) {
				foreach ( $post_types as $post_type ) {
					$new_source_key              = str_replace( 'POSTTYPE', $post_type, $source_key );
					$new_target_key              = false === $target_key ? false : str_replace( 'POSTTYPE', $post_type, $target_key );
					$mappings[ $new_source_key ] = $new_target_key;
				}
			} elseif ( $this->is_taxonomy_option( $source_key ) ) {
				foreach ( $taxonomies as $taxonomy ) {
					$new_source_key              = str_replace( 'TAXONOMY', $taxonomy, $source_key );
					$new_target_key              = false === $target_key ? false : str_replace( 'TAXONOMY', $taxonomy, $target_key );
					$mappings[ $new_source_key ] = $new_target_key;
				}
			}
		}

		return $mappings;
	}

	protected function get_post_types() {
		return get_post_types( array( 'public' => true ) );
	}

	/**
	 * @return array
	 */
	protected function get_taxonomies() {
		return array_merge(
			array( 'post_tag', 'category' ),
			get_taxonomies(
				array(
					'_builtin' => false,
					'public'   => true,
				)
			)
		);
	}

	private function is_custom_type_option( $key ) {
		return $this->is_post_type_option( $key ) || $this->is_taxonomy_option( $key );
	}

	private function is_post_type_option( $key ) {
		return $key && strpos( $key, 'POSTTYPE' ) !== false;
	}

	private function is_taxonomy_option( $key ) {
		return $key && strpos( $key, 'TAXONOMY' ) !== false;
	}

	protected function get_custom_handlers() {
		return array();
	}

	protected function pre_process_value( $target_key, $source_value ) {
		if ( $this->requires_array_wrapping( $target_key ) ) {
			return array( $source_value );
		}

		if ( $this->requires_boolean_casting( $target_key ) ) {
			return $this->is_value_truthy( $source_value );
		}

		if ( $this->requires_boolean_inversion( $target_key ) ) {
			return ! $this->is_value_truthy( $source_value );
		}

		$all_arguments = func_get_args();

		return $this->try_custom_pre_processor( $target_key, $source_value, $all_arguments );
	}

	private function requires_array_wrapping( $key ) {
		return $key && strpos( $key, '[]' ) !== false;
	}

	private function requires_boolean_casting( $key ) {
		return $key && strpos( $key, '!!' ) !== false;
	}

	private function is_value_truthy( $value ) {
		return 'on' === $value || '1' === $value || true === $value || ( is_int( $value ) && $value > 0 );
	}

	private function requires_boolean_inversion( $key ) {
		return $key && strpos( $key, '!' ) !== false;
	}

	private function try_custom_pre_processor( $target_key, $source_value, $all_arguments ) {
		$pre_processor = null;
		foreach ( $this->get_pre_processors() as $pattern => $callback ) {
			if ( ! is_null( $target_key ) && preg_match( '#' . $pattern . '#', $target_key ) ) {
				$pre_processor = $callback;
			}
		}

		if ( ! $pre_processor ) {
			return $source_value;
		}

		return call_user_func_array(
			array( $this, $pre_processor ),
			$all_arguments
		);
	}

	protected function get_pre_processors() {
		return array();
	}

	protected function pre_process_key( $key ) {
		if ( $this->requires_array_wrapping( $key ) ) {
			$key = $this->remove_array_wrapping_indicators( $key );
		}

		if ( $this->requires_boolean_casting( $key ) ) {
			$key = $this->remove_boolean_casting_indicators( $key );
		}

		if ( $this->requires_boolean_inversion( $key ) ) {
			$key = $this->remove_boolean_inversion_indicators( $key );
		}

		if ( $this->is_multipart_key( $key ) ) {
			$key = $this->get_key_parts( $key );
		}

		return $key;
	}

	private function remove_array_wrapping_indicators( $key ) {
		$parts = explode( '[]', $key );

		return empty( $parts[0] ) ? '' : $parts[0];
	}

	private function remove_boolean_casting_indicators( $key ) {
		$parts = explode( '!!', $key );

		return empty( $parts[1] ) ? '' : $parts[1];
	}

	private function remove_boolean_inversion_indicators( $key ) {
		$parts = explode( '!', $key );

		return empty( $parts[1] ) ? '' : $parts[1];
	}

	private function is_multipart_key( $key ) {
		return $key && strpos( $key, '/' ) !== false;
	}

	private function get_key_parts( $key ) {
		return explode( '/', $key );
	}

	/**
	 * @param int $total_sites     Total sites.
	 * @param int $completed_sites Completed sites.
	 */
	private function update_site_status( $total_sites, $completed_sites ) {
		$this->update_status(
			array(
				'total_sites'     => $total_sites,
				'completed_sites' => $completed_sites,
			)
		);
	}
}